Você já viu :where() e :is() em algum código CSS moderno. Talvez tenha até consultado a MDN. "São pseudo-classes para agrupar seletores", certo?
Aí você tenta usar e... não entende quando cada um faz diferença. A sintaxe funciona, mas o comportamento às vezes é inesperado. Você acaba voltando pros seletores tradicionais porque "pelo menos funcionam".
Durante meses, ignorei esses seletores completamente. Achava que eram apenas "syntax sugar" sem benefício real. Até o dia que meu CSS escalou pra mais de 50 componentes e virou um inferno de !important e conflitos de especificidade.
Foi aí que esses seletores finalmente fizeram sentido.
O Problema Invisível Que Você Só Vê Quando Dói
Especificidade em CSS é silenciosa. Você não vê, não debugga facilmente, e só percebe quando já está no meio do caos.
Pensa nesse cenário super comum:
/* Você cria estilos base para headings */
.card h2,
.panel h2,
.modal h2 {
font-size: 1.5rem;
color: #333;
}
/* Semanas depois, precisa mudar SÓ o modal */
.modal h2 {
font-size: 1.2rem; /* Por que não funciona??? */
}Ambos têm especificidade (0,0,2,0). Empate. O último deveria ganhar, mas... e se tem outros seletores no meio? E se a ordem dos arquivos muda? E se tem CSS de biblioteca externa?
Solução clássica: aumentar especificidade.
.modal .header h2 { /* Agora sim! */
font-size: 1.2rem;
}
/* Até precisar mudar de novo... */
.modal .header.compact h2 { /* Mais especificidade! */
font-size: 1.1rem;
}
/* E de novo... */
.modal .header.compact.dark h2 { /* WTF */
font-size: 1.1rem !important; /* Desisto */
}Esse ciclo vicioso é o verdadeiro problema que :where() e :is() resolvem.
A Diferença Que Ninguém Explica Direito
:where() tem especificidade ZERO.
:is() mantém a especificidade do seletor mais específico dentro dele.
Parece detalhe técnico? É a diferença entre CSS escalável e inferno de manutenção.
Especificidade em números
Quando você escreve .card h2:
Especificidade:
(0,0,2,0)- uma classe + um elemento
Quando escreve :where(.card) h2:
Especificidade:
(0,0,0,1)- :where() conta ZERO + um elemento
Quando escreve :is(.card, #container) h2:
Especificidade:
(0,1,0,1)- o #container (mais específico) + um elemento
Percebe? O conteúdo dentro de :where() é completamente ignorado no cálculo de especificidade.
Exemplo Real: Reset CSS Que Não Quebra Tudo
Sabe aqueles resets CSS que você copia e depois precisa sobrescrever com !important? :where() resolve isso.
/* Reset tradicional - especificidade normal */
h1, h2, h3, h4, h5, h6 {
margin: 0;
padding: 0;
font-weight: normal;
}
/* Agora tenta sobrescrever em um componente */
.article h2 {
margin-bottom: 1rem; /* Funciona, mas... */
}
/* Problema aparece aqui */
.card h2 {
margin-bottom: 0.5rem; /* Também funciona */
}
/* Mas e se você combinar? */
.article .card h2 {
/* Qual margin ganha? Depende da ordem no CSS! */
}Agora com :where():
/* Reset com especificidade ZERO */
:where(h1, h2, h3, h4, h5, h6) {
margin: 0;
padding: 0;
font-weight: normal;
}
/* QUALQUER seletor sobrescreve facilmente */
.article h2 {
margin-bottom: 1rem; /* Ganha sempre */
}
h2 {
margin-bottom: 2rem; /* Até isso ganha! */
}
/* Sem guerra de especificidade */
.article .card h2 {
margin-bottom: 0.75rem; /* Previsível */
}Repare: o reset existe como "piso", não como obstáculo.
Caso Real: Componentes Com Estados
Imagine botões que precisam de hover, focus, active, disabled... em múltiplos contextos.
Abordagem antiga (pesadelo de manutenção)
/* Você escreve isso... */
.btn:hover,
.btn:focus,
.btn:active,
.link:hover,
.link:focus,
.link:active,
.nav-item:hover,
.nav-item:focus,
.nav-item:active {
color: blue;
border-color: blue;
}
/* E quando adiciona novo estado? */
.btn:hover,
.btn:focus,
.btn:active,
.btn:disabled, /* +1 linha */
.link:hover,
.link:focus,
.link:active,
.link:disabled, /* +1 linha */
.nav-item:hover,
.nav-item:focus,
.nav-item:active,
.nav-item:disabled { /* +1 linha */
/* ... */
}
/* 12 linhas para 3 componentes × 4 estados = insano */Com :is() (legível e mantível)
/* Uma linha. Fim. */
:is(.btn, .link, .nav-item):is(:hover, :focus, :active, :disabled) {
color: blue;
border-color: blue;
}
/* Adicionar novo componente? */
:is(.btn, .link, .nav-item, .badge):is(:hover, :focus, :active, :disabled) {
/* Literalmente adicionar palavra na lista */
}Por que isso funciona?
:is(.btn, .link, .nav-item) tem especificidade da classe mais específica: (0,0,1,0).
Todos os componentes ali dentro ficam com a mesma especificidade, facilitando override quando necessário.
Erro Clássico: Confundir Os Dois
Durante semanas, eu usava :is() pra tudo. "Se funciona, por que complicar?"
/* Eu fazia isso */
:is(.card, .panel, .modal) h2 {
font-size: 1.5rem;
}
/* E depois ficava confuso porque era difícil sobrescrever */
.modal h2 {
font-size: 1.2rem; /* Às vezes funcionava, às vezes não */
}O que acontece:
:is(.card, .panel, .modal) h2 tem especificidade (0,0,1,1) - igual a .modal h2.
Quando tem empate, a ordem no CSS importa. Se o :is() vem depois, ele ganha. Imprevisível.
Solução
Use :where() quando quer criar base facilmente sobrescrevível:
/* Base com especificidade zero */
:where(.card, .panel, .modal) h2 {
font-size: 1.5rem;
}
/* Sobrescreve sem drama */
.modal h2 {
font-size: 1.2rem; /* Sempre funciona */
}Use :is() quando quer agrupar mantendo lógica de especificidade:
/* Agrupar estados - especificidade controlada */
.card:is(:hover, :focus) {
border-color: blue;
}Design System Real: Camadas de Especificidade
Aqui está um padrão que uso em todo projeto agora:
/* CAMADA 1: Reset universal (especificidade 0) */
:where(button, input, select, textarea) {
font-family: inherit;
font-size: 100%;
margin: 0;
}
/* CAMADA 2: Estilos base de componentes */
.btn {
padding: 0.5rem 1rem;
border-radius: 4px;
background: var(--primary);
}
/* CAMADA 3: Variantes com :is() */
.btn:is(.small, .compact) {
padding: 0.25rem 0.5rem;
}
/* CAMADA 4: Contextos específicos */
:is(.header, .footer) .btn {
padding: 0.375rem 0.75rem;
}
/* CAMADA 5: Estados */
.btn:is(:hover, :focus-visible) {
background: var(--primary-dark);
}Cada camada tem especificidade crescente de forma previsível. Nada de surpresas.
Quando Usar Cada Um
✅ Use :where() para:
Resets e bases universais
:where(h1, h2, h3, h4, h5, h6) {
line-height: 1.2;
}Estilos padrão que você QUER que sejam sobrescritos
:where(.card, .panel) {
padding: 1rem;
border: 1px solid #ddd;
}Design tokens aplicados amplamente
:where(.btn, .link, .badge) {
transition: all 0.2s ease;
}✅ Use :is() para:
Agrupar estados mantendo especificidade
.input:is(:hover, :focus, :active) {
border-color: var(--accent);
}Simplificar seletores complexos
/* Antes */
.sidebar a:hover,
.sidebar a:focus,
.footer a:hover,
.footer a:focus {
text-decoration: underline;
}
/* Depois */
:is(.sidebar, .footer) a:is(:hover, :focus) {
text-decoration: underline;
}Criar variantes de componentes
.btn:is(.primary, .success, .danger) {
color: white;
}❌ Evite usar quando:
Um seletor simples já resolve
/* Não precisa */
:where(.btn) {
background: blue;
}
/* Melhor */
.btn {
background: blue;
}Você quer alta especificidade de propósito
/* Se precisa garantir que isso NÃO seja sobrescrito, não use :where() */
.modal.critical {
z-index: 9999;
}Compatibilidade: Você Pode Usar Hoje
:where() e :is() funcionam em:
Chrome/Edge 88+
Firefox 78+
Safari 14+
Isso cobre 95%+ dos usuários em 2024.
Para browsers antigos, você pode usar progressive enhancement:
/* Fallback para browsers sem suporte */
.card,
.panel,
.modal {
padding: 1rem;
}
/* Sobrescrever com :where() em browsers modernos */
@supports selector(:where(*)) {
:where(.card, .panel, .modal) {
padding: 1rem;
}
/* Limpar fallback */
.card,
.panel,
.modal {
all: unset;
}
}Resumo: O Que Realmente Importa
:where() = especificidade zero = base sobrescrevível
Use pra resets
Use pra estilos universais
Use quando quer facilitar overrides
:is() = agrupa sem mudar comportamento de especificidade
Use pra estados
Use pra simplificar código
Use quando quer legibilidade
Não é sobre decorar sintaxe. É sobre ter controle sobre especificidade ao invés de lutar contra ela.
Próximos Passos
Abra um projeto e procure por:
Resets que você sobrescreve com
!important→ Refatore com :where()Listas longas de seletores com vírgula → Simplifique com :is()
Guerras de especificidade → Crie camadas claras com :where() e :is()
Você vai se perguntar como viveu sem isso. Sério.
Dúvidas? Comenta que eu ajudo! 💪
