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.
- 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.
- 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
@containere 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;
}
}