Combinações de seletor e regra @

O problema

Muitas vezes, precisamos combinar critérios de filtragem de tipos diferentes, como seletores + consultas de mídia, consultas de mídia + consultas de contêiner etc.

Por exemplo, combinar (prefers-color-scheme: dark) com um seletor que segmenta substituições de esquema de cores (por exemplo, :root:has(> head > meta[name="color-scheme"][content="dark"]) ou :root.dark) para aplicar determinados estilos no modo claro/escuro, independentemente de como o esquema de cores foi definido.

A abordagem simples leva a um código duplicado:

/* Light mode styles */
.card.featured {
  font-weight: 400;
}

#logo-dark {
  display: none;
}

@media (prefers-color-scheme: dark) {
  /* Dark mode styles */
  :root:not(.light) {
    .card.featured {
      font-weight: 300;
    }

    #logo-dark {
      display: inline;
    }

    #logo {
      display: none;
    }
  }
}

/* Dark mode styles, again */
:root.dark {
  .card.featured {
    font-weight: 300;
  }

  #logo-dark {
    display: inline;
  }

  #logo {
    display: none;
  }
}

O que fazer em vez disso

Armazene o resultado do filtro combinado em uma propriedade personalizada e use consultas de estilo de contêiner para definir regras com base nessa propriedade.

IMPORTANTE: devido ao funcionamento das consultas de contêiner, essa técnica só funciona se for possível definir a propriedade personalizada em um ancestral de qualquer regra que precise ser correspondida.

1. Desvincular a lógica de seleção das regras aplicadas

:root {
  --color-scheme: light;

  @media (prefers-color-scheme: dark) {
    --color-scheme: dark;
  }

  &:has(> head > meta[name="color-scheme"][content="dark"]) {
    --color-scheme: dark;
  }

  &:has(> head > meta[name="color-scheme"][content="light"]) {
    --color-scheme: light;
  }
}

2. Chave na propriedade personalizada para regras aplicadas

@container not style(--color-scheme: dark) {
  .card.featured {
    font-weight: 400;
  }

  #logo-dark {
    display: none;
  }
}

@container style(--color-scheme: dark) {
  .card.featured {
    font-weight: 300;
  }

  #logo {
    display: none;
  }
}

3. OPCIONAL: classes de utilitários para alternar a visibilidade

Um caso muito comum é alternar a visibilidade com base na correspondência dos critérios. As classes de utilitário precisam prevalecer sobre os estilos de componente arbitrários (daí o !important), e o display mostrado é desconhecido. Por isso, elas não podem usar substituições. Use not style(...) para controlá-las:

@container not style(--color-scheme: dark) {
  .if-dark {
    display: none !important;
  }
}

@container not style(--color-scheme: light) {
  .if-light {
    display: none !important;
  }
}

Substitutos

Há dois tipos de substitutos, dependendo de quão crítica é a ramificação e do que constitui um substituto aceitável.

  1. Para ramificações críticas, em que todos os estados precisam funcionar corretamente mesmo que as consultas de estilo não sejam compatíveis, não é possível usar consultas de estilo. Em vez disso, use a abordagem simples acima, duplicando as regras para cada estado. As classes de utilitários ainda podem ser usadas, desde que também sejam definidas dessa forma.
  2. Para ramificações não críticas, em que um estado padrão é aceitável, é possível definir os estilos de base fora de qualquer regra @container e usar apenas consultas de estilo como substituições. Isso preserva a maior parte do espírito dessa abordagem, mas a principal desvantagem é o acoplamento de valores que as substituições introduzem.
#logo-dark {
  display: none;
}

@container style(--color-scheme: dark) {
  #logo-dark {
    display: inline; /* override */
  }

  #logo {
    display: none;
  }
}