Escolher uma sintaxe para aninhamento de CSS

Duas sintaxes concorrentes precisam da sua ajuda para determinar qual delas deve ser defendida até um candidato a especificação.

O aninhamento de CSS é uma adição de sintaxe conveniente que permite adicionar CSS dentro de um conjunto de regras. Se você já usou SCSS, Less ou Stylus, com certeza já viu algumas variações disso:

.nesting {
  color: hotpink;

  > .is {
    color: rebeccapurple;

    > .awesome {
      color: deeppink;
    }
  }
}

que, depois de compilado para CSS regular pelo pré-processador, se transforma em CSS regular como este:

.nesting {
  color: hotpink;
}

.nesting > .is {
  color: rebeccapurple;
}

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

Uma versão oficial em CSS dessa sintaxe está sendo considerada, e temos uma divisão de preferências que gostaríamos de resolver com a ajuda da comunidade. O restante desta postagem vai apresentar as opções de sintaxe para que você possa responder a uma pesquisa no final.

Por que o exemplo exato de aninhamento mostrado acima não pode ser a sintaxe para aninhamento de CSS?

Há alguns motivos para a sintaxe de aninhamento mais usada não poder ser usada como está:

  1. Análise ambígua
    Alguns seletores aninhados podem parecer exatamente propriedades e pré-processadores são capazes de resolver e gerenciar esses seletores no momento da build. Os mecanismos de navegador não terão as mesmas affordances, e os seletores nunca podem ser interpretados de forma imprecisa.

  2. Conflitos de análise do pré-processador
    A maneira de aninhamento do CSS não deve interromper os pré-processadores nem os fluxos de trabalho de aninhamento do desenvolvedor atuais. Isso seria prejudicial e desrespeitoso com esses ecossistemas e comunidades.

  3. Aguardando :is()
    O aninhamento básico não precisa de :is(), mas o aninhamento mais complexo precisa. Consulte o Exemplo 3 para uma introdução simples às listas de seletores e ao aninhamento. Imagine que essa lista de seletores esteja no meio de um seletor, e não no início. Nesses casos, :is() é necessário para agrupar os seletores no meio de outro seletor.

Visão geral do que estamos comparando

Queremos fazer o aninhamento de CSS da maneira certa e, por isso, incluímos a comunidade. As seções a seguir descrevem as três versões possíveis que estamos avaliando. Em seguida, vamos analisar alguns exemplos de uso para comparação e, no final, haverá uma pesquisa simples perguntando qual você preferiu no geral.

Opção 1: @nest

Essa é a sintaxe especificada atual em CSS Nesting 1. Ele oferece uma maneira conveniente de aninhar estilos de anexação iniciando novos seletores aninhados com &. Ele também oferece @nest como uma maneira de colocar o contexto & em qualquer lugar dentro de um novo seletor, como quando você não está apenas anexando assuntos. Ele é flexível e mínimo, mas exige que você se lembre de @nest ou &, dependendo do seu caso de uso.

Opção 2: @nest restricted

Essa é uma alternativa mais restrita, na tentativa de reduzir a despesa mencionada de lembrar dois métodos de aninhamento. Essa sintaxe restrita permite que o aninhamento ocorra somente após @nest. Portanto, não há um padrão de conveniência de adição apenas. Removendo a ambiguidade de escolha, criando uma maneira fácil de lembrar de aninhar, mas sacrificando a concisão em favor da convenção.

Opção 3: colchetes

Para evitar a sintaxe dupla ou a confusão extra envolvida nas propostas de @nest, Miriam Suzanne e Elika Etemad propuseram uma sintaxe alternativa que, em vez disso, depende de chaves extras. Isso oferece clareza de sintaxe, com apenas dois caracteres extras e sem novas regras @. Ele também permite que regras aninhadas sejam agrupadas pelo tipo de aninhamento necessário, simplificando vários seletores aninhados de maneira semelhante.

Exemplo 1: aninhamento direto

@nest

.foo {
  color: #111;

  & .bar {
    color: #eee;
  }
}

@nest always

.foo {
  color: #111;

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

colchetes

.foo {
  color: #111;

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

CSS equivalente

.foo {
  color: #111;
}

.foo .bar {
  color: #eee;
}

Exemplo 2: aninhamento composto

@nest

.foo {
  color: blue;

  &.bar {
    color: red;
  }
}

@nest always

.foo {
  color: blue;

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

colchetes

.foo {
  color: blue;

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

CSS equivalente

.foo {
  color: blue;
}

.foo.bar {
  color: red;
}

Exemplo 3: listas de seletores e aninhamento

@nest

.foo, .bar {
  color: blue;

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

@nest always

.foo, .bar {
  color: blue;

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

colchetes

.foo, .bar {
  color: blue;

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

CSS equivalente

.foo, .bar {
  color: blue;
}

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

Exemplo 4: vários níveis

@nest

figure {
  margin: 0;

  & > figcaption {
    background: lightgray;

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

@nest always

figure {
  margin: 0;

  @nest & > figcaption {
    background: lightgray;

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

colchetes

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;
}

Exemplo 5: aninhamento de elementos principais ou mudança de assunto

@nest

.foo {
  color: red;

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

@nest always

.foo {
  color: red;

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

colchetes

.foo {
  color: red;

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

CSS equivalente

.foo {
  color: red;
}

.parent .foo {
  color: blue;
}

Exemplo 6: combinação de aninhamento direto e de elementos pai

@nest

.foo {
  color: blue;

  @nest .bar & {
    color: red;

    &.baz {
      color: green;
    }
  }
}

@nest always

.foo {
  color: blue;

  @nest .bar & {
    color: red;

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

colchetes

.foo {
  color: blue;

  {
    .bar & {
      color: red;

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

CSS equivalente

.foo {
  color: blue;
}

.bar .foo {
  color: red;
}

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

Exemplo 7: aninhamento de consultas de mídia

@nest

.foo {
  display: grid;

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

ou explícita / estendida

.foo {
  display: grid;

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

@nest always (é sempre explícito)

.foo {
  display: grid;

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

colchetes

.foo {
  display: grid;

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

ou explícita / estendida

.foo {
  display: grid;

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

CSS equivalente

.foo {
  display: grid;
}

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

Exemplo 8: grupos de aninhamento

@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 always

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;
    }
  }
}

colchetes

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;
}

Exemplo 9: grupo de aninhamento complexo "Pia de cozinha"

@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 always

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;
  }
}

colchetes

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 você tenha achado essa uma comparação justa e uma amostra das opções de sintaxe que estamos avaliando. Analise com atenção e informe abaixo qual você prefere. Agradecemos por ajudar a avançar o aninhamento de CSS para uma sintaxe que todos vamos conhecer e adorar.

Participe da pesquisa!