Ayuda a elegir una sintaxis para anidar CSS

Dos sintaxis en competencia necesitan tu ayuda para determinar cuál debe defenderse hasta convertirse en candidata a especificación.

El anidamiento de CSS es una adición de sintaxis conveniente que permite agregar CSS dentro de un conjunto de reglas. Si usaste SCSS, Less o Stylus, seguramente viste algunas variantes de este código:

.nesting {
  color: hotpink;

  > .is {
    color: rebeccapurple;

    > .awesome {
      color: deeppink;
    }
  }
}

Después de que el preprocesador lo compila en CSS normal, se convierte en CSS normal de la siguiente manera:

.nesting {
  color: hotpink;
}

.nesting > .is {
  color: rebeccapurple;
}

.nesting > .is > .awesome {
  color: deeppink;
}

Se está considerando seriamente una versión oficial en CSS de esta sintaxis, y tenemos una división en las preferencias que nos gustaría resolver con la ayuda de la comunidad. En el resto de esta entrada, se presentarán las opciones de sintaxis para que puedas responder una encuesta al final.

¿Por qué el ejemplo de anidación exacto que se muestra arriba no puede ser la sintaxis para la anidación de CSS?

Existen algunos motivos por los que la sintaxis de anidación más popular no se puede usar tal como está:

  1. Análisis ambiguo
    Algunos selectores anidados pueden parecerse exactamente a las propiedades y los preprocesadores, y pueden resolverse y administrarse en el momento de la compilación. Los motores de los navegadores no tendrán las mismas posibilidades, por lo que los selectores nunca se deben interpretar de forma imprecisa.

  2. Conflictos de análisis del preprocesador
    La forma de anidación de CSS no debería interrumpir los preprocesadores ni los flujos de trabajo de anidación existentes de los desarrolladores. Esto sería perjudicial y desconsiderado para esos ecosistemas y comunidades.

  3. Waiting for :is()
    El anidamiento básico no necesita :is(), pero el anidamiento más complejo sí. Consulta el ejemplo 3 para ver una breve introducción a las listas de selectores y el anidamiento. Imagina que esa lista de selectores estuviera en el medio de un selector en lugar de al principio. En esos casos, se requiere :is() para agrupar los selectores en el medio de otro selector.

Descripción general de lo que comparamos

Queremos que el anidamiento de CSS funcione correctamente y, para ello, incluimos a la comunidad. En las siguientes secciones, se describirán las tres versiones posibles que estamos evaluando. Luego, revisaremos algunos ejemplos de uso para comparar y, al final, habrá una breve encuesta en la que se te preguntará cuál prefieres en general.

Opción 1: @nest

Esta es la sintaxis especificada actual en CSS Nesting 1. Ofrece una forma conveniente de anidar estilos de anexión iniciando nuevos selectores anidados con &. También ofrece @nest como una forma de colocar el contexto & en cualquier lugar dentro de un selector nuevo, como cuando no solo agregas sujetos. Es flexible y minimalista, pero a costa de tener que recordar @nest o & según tu caso de uso.

Opción 2: @nest restricted

Esta es una alternativa más estricta, en un intento por reducir el gasto mencionado de recordar dos métodos de anidación. Esta sintaxis restringida solo permite que el anidamiento se produzca después de @nest, por lo que no hay un patrón de conveniencia de solo agregar. Elimina la ambigüedad de la elección, crea una forma fácil de recordar para anidar, pero sacrifica la brevedad en favor de la convención.

Opción 3: Corchetes

Para evitar la doble sintaxis o el desorden adicional que implican las propuestas de @nest, Miriam Suzanne y Elika Etemad propusieron una sintaxis alternativa que, en cambio, se basa en corchetes adicionales. Esto proporciona claridad en la sintaxis, con solo dos caracteres adicionales y sin nuevas reglas @. También permite agrupar las reglas anidadas según el tipo de anidamiento requerido, como una forma de simplificar varios selectores anidados de manera similar.

Ejemplo 1: Anidación directa

@nest

.foo {
  color: #111;

  & .bar {
    color: #eee;
  }
}

@nest siempre

.foo {
  color: #111;

  @nest & .bar {
    color: #eee;
  }
}

corchetes

.foo {
  color: #111;

  {
    & .bar {
      color: #eee;
    }
  }
}

CSS equivalente

.foo {
  color: #111;
}

.foo .bar {
  color: #eee;
}

Ejemplo 2: Anidación compuesta

@nest

.foo {
  color: blue;

  &.bar {
    color: red;
  }
}

@nest siempre

.foo {
  color: blue;

  @nest &.bar {
    color: red;
  }
}

corchetes

.foo {
  color: blue;

  {
    &.bar {
      color: red;
    }
  }
}

CSS equivalente

.foo {
  color: blue;
}

.foo.bar {
  color: red;
}

Ejemplo 3: Listas de selectores y anidación

@nest

.foo, .bar {
  color: blue;

  & + .baz,
  &.qux {
    color: red;
  }
}

@nest siempre

.foo, .bar {
  color: blue;

  @nest & + .baz,
  &.qux {
    color: red;
  }
}

corchetes

.foo, .bar {
  color: blue;

  {
    & + .baz,
    &.qux {
      color: red;
    }
  }
}

CSS equivalente

.foo, .bar {
  color: blue;
}

:is(.foo, .bar) + .baz,
:is(.foo, .bar).qux {
  color: red;
}

Ejemplo 4: Varios niveles

@nest

figure {
  margin: 0;

  & > figcaption {
    background: lightgray;

    & > p {
      font-size: .9rem;
    }
  }
}

@nest siempre

figure {
  margin: 0;

  @nest & > figcaption {
    background: lightgray;

    @nest & > p {
      font-size: .9rem;
    }
  }
}

corchetes

figure {
  margin: 0;

  {
    & > figcaption {
      background: lightgray;

      {
        & > p {
          font-size: .9rem;
        }
      }
    }
  }
}

CSS equivalente

figure {
  margin: 0;
}

figure > figcaption {
  background: hsl(0 0% 0% / 50%);
}

figure > figcaption > p {
  font-size: .9rem;
}

Ejemplo 5: Anidación de elementos principales o cambio de tema

@nest

.foo {
  color: red;

  @nest .parent & {
    color: blue;
  }
}

@nest siempre

.foo {
  color: red;

  @nest .parent & {
    color: blue;
  }
}

corchetes

.foo {
  color: red;

  {
    .parent & {
      color: blue;
    }
  }
}

CSS equivalente

.foo {
  color: red;
}

.parent .foo {
  color: blue;
}

Ejemplo 6: Combinación de anidamiento directo y superior

@nest

.foo {
  color: blue;

  @nest .bar & {
    color: red;

    &.baz {
      color: green;
    }
  }
}

@nest siempre

.foo {
  color: blue;

  @nest .bar & {
    color: red;

    @nest &.baz {
      color: green;
    }
  }
}

corchetes

.foo {
  color: blue;

  {
    .bar & {
      color: red;

      {
        &.baz {
          color: green;
        }
      }
    }
  }
}

CSS equivalente

.foo {
  color: blue;
}

.bar .foo {
  color: red;
}

.bar .foo.baz {
  color: green;
}

Ejemplo 7: Anidamiento de consultas de medios

@nest

.foo {
  display: grid;

  @media (width => 30em) {
    grid-auto-flow: column;
  }
}

o explícita / extendida

.foo {
  display: grid;

  @media (width => 30em) {
    & {
      grid-auto-flow: column;
    }
  }
}

@nest siempre (siempre es explícito)

.foo {
  display: grid;

  @media (width => 30em) {
    @nest & {
      grid-auto-flow: column;
    }
  }
}

corchetes

.foo {
  display: grid;

  @media (width => 30em) {
    grid-auto-flow: column;
  }
}

o explícita / extendida

.foo {
  display: grid;

  @media (width => 30em) {
    & {
      grid-auto-flow: column;
    }
  }
}

CSS equivalente

.foo {
  display: grid;
}

@media (width => 30em) {
  .foo {
    grid-auto-flow: column;
  }
}

Ejemplo 8: Anidación de grupos

@nest

fieldset {
  border-radius: 10px;

  &:focus-within {
    border-color: hotpink;
  }

  & > legend {
    font-size: .9em;
  }

  & > div {
    & + div {
      margin-block-start: 2ch;
    }

    & > label {
      line-height: 1.5;
    }
  }
}

@nest siempre

fieldset {
  border-radius: 10px;

  @nest &:focus-within {
    border-color: hotpink;
  }

  @nest & > legend {
    font-size: .9em;
  }

  @nest & > div {
    @nest & + div {
      margin-block-start: 2ch;
    }

    @nest & > label {
      line-height: 1.5;
    }
  }
}

corchetes

fieldset {
  border-radius: 10px;

  {
    &:focus-within {
      border-color: hotpink;
    }
  }

  > {
    legend {
      font-size: .9em;
    }

    div {
      + div {
        margin-block-start: 2ch;
      }

      > label {
        line-height: 1.5;
      }
    }}
  }
}

CSS equivalente

fieldset {
  border-radius: 10px;
}

fieldset:focus-within {
  border-color: hotpink;
}

fieldset > legend {
  font-size: .9em;
}

fieldset > div + div {
  margin-block-start: 2ch;
}

fieldset > div > label {
  line-height: 1.5;
}

Ejemplo 9: Grupo de anidación complejo "Kitchen Sink"

@nest

dialog {
  border: none;

  &::backdrop {
    backdrop-filter: blur(25px);
  }

  & > form {
    display: grid;

    & > :is(header, footer) {
      align-items: flex-start;
    }
  }

  @nest html:has(&[open]) {
    overflow: hidden;
  }
}

@nest siempre

dialog {
  border: none;

  @nest &::backdrop {
    backdrop-filter: blur(25px);
  }

  @nest & > form {
    display: grid;

    @nest & > :is(header, footer) {
      align-items: flex-start;
    }
  }

  @nest html:has(&[open]) {
    overflow: hidden;
  }
}

corchetes

dialog {
  border: none;

  {
    &::backdrop {
      backdrop-filter: blur(25px);
    }

    & > form {
      display: grid;

      {
        & > :is(header, footer) {
          align-items: flex-start;
        }
      }
    }
  }

  {
    html:has(&[open]) {
      overflow: hidden;
    }
  }
}

CSS equivalente

dialog {
  border: none;
}

dialog::backdrop {
  backdrop-filter: blur(25px);
}

dialog > form {
  display: grid;
}

dialog > form > :is(header, footer) {
  align-items: flex-start;
}

html:has(dialog[open]) {
  overflow: hidden;
}

Hora de votar

Esperamos que consideres que esta fue una comparación justa y una muestra de las opciones de sintaxis que estamos evaluando. Revísalas con atención y, luego, indícanos cuál prefieres. Agradecemos tu ayuda para avanzar en el anidamiento de CSS y lograr una sintaxis que todos conoceremos y amaremos.

Responder la encuesta