Ayuda a elegir una sintaxis para anidar CSS

Dos sintaxis en competencia necesitan tu ayuda para determinar cuál debe ser la candidata a especificación.

Adam Argyle
Adam Argyle
Miriam Suzanne
Miriam Suzanne

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, entonces es muy probable que hayas visto algunos de estos formatos:

.nesting {
  color: hotpink;

  > .is {
    color: rebeccapurple;

    > .awesome {
      color: deeppink;
    }
  }
}

Que, 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 de 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 publicación, 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 el anidamiento 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 verse exactamente como propiedades y preprocesadores, que pueden resolverlos y administrarlos en el tiempo de compilación. Los motores de navegador no tendrán los mismos indicadores visuales, por lo que los selectores nunca deben interpretarse de forma imprecisa.

  2. Conflictos de análisis del preprocesador
    La forma en que CSS anida no debe interrumpir los preprocesadores ni los flujos de trabajo de anidación existentes de los desarrolladores. Esto sería disruptivo y desconsiderado con esos ecosistemas y comunidades.

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

Descripción general de lo que comparamos

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

Opción 1: @nest

Esta es la sintaxis especificada actual en anidado de CSS 1. Ofrece una forma conveniente de anidar estilos de inserció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 debes recordar @nest o & según tu caso de uso.

Opción 2: @nest restringido

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 la anidación se produzca después de @nest, por lo que no hay un patrón de conveniencia de solo inserción. Quita la ambigüedad de la elección, crea una forma fácil de recordar para anidar, pero sacrifica la concisión en favor de la convención.

Opción 3: Corchetes

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

Ejemplo 1: Anidamiento directo

@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: Anidamiento compuesto

@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: Anidamiento y listas de selectores

@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 superiores o cambio de asunto

@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 anidación directa 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 de forma explícita o extendida

.foo {
  display: grid;

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

@nest always (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 de forma explícita o 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

Espero que consideres que esta fue una comparación justa y una muestra de las opciones de sintaxis que estamos evaluando. Revísalos con cuidado y cuéntanos cuál prefieres a continuación. Gracias por ayudarnos a avanzar en el anidamiento de CSS a una sintaxis que todos conoceremos y amaremos.

Responde la encuesta.