Frontend

CSS :where() e :is(): Especificidade, Resets e Manutenção

Muitos desenvolvedores ignoram as pseudo-classes CSS :where() e :is(), vendo-as como "syntax sugar". Este artigo revela como elas são cruciais para resolver o caos da especificidade, permitindo criar CSS escalável e manutenível. Entenda a diferença e aplique em resets, componentes e design systems.

CSS :where() e :is(): Especificidade, Resets e Manutenção

Você já escreveu o mesmo bloco de CSS três vezes só mudando o seletor? Essa é exatamente a dor que :is() e :where() resolvem — e a diferença entre os dois é mais importante do que parece.

Esse CSS te parece familiar:

TEXT
header a,footer a,nav a {  color: blue;  text-decoration: none;}

Repetindo a mesma regra pra três contextos diferentes. Agora imagina que você tem dez contextos. E que cada um tem três variações de estado — :hover, :focus, :visited. O arquivo CSS vira uma lista telefônica.

O CSS moderno tem uma resposta pra isso. Duas, na verdade — e elas parecem iguais mas se comportam diferente num detalhe que vai te salvar de bugs sutis.

:is() — agrupe seletores sem repetição

O :is() aceita uma lista de seletores e aplica o estilo pra qualquer um que bater. O bloco acima vira:

TEXT
:is(header, footer, nav) a {  color: blue;  text-decoration: none;}

Uma linha, mesmo resultado. O navegador interpreta isso exatamente como a versão repetida — semanticamente são equivalentes.

Fica mais interessante quando você combina com pseudo-classes:

TEXT
/* Antes — repetindo pra cada estado */button:hover,button:focus,button:active {  background: darkblue;}/* Depois — limpo */button:is(:hover, :focus, :active) {  background: darkblue;}

Ou pra estilizar headings de uma vez:

TEXT
/* Todos os headings dentro de article */article :is(h1, h2, h3, h4) {  font-family: Georgia, serif;  line-height: 1.3;}

:where() — igualzinho, mas sem peso de especificidade

Aqui mora a diferença que importa.

Especificidade em CSS é o sistema de pontuação que decide qual regra vence quando duas se contradizem. Um seletor de ID vale mais que uma classe, que vale mais que uma tag. Quando você usa :is(), a especificidade do seletor mais forte dentro dos parênteses se propaga pra regra inteira.

TEXT
/* :is() herda a especificidade do #header — alta */:is(#header, .nav, footer) a {  color: blue;}/* ↑ esse seletor tem especificidade de ID   por causa do #header, mesmo que o elemento   seja .nav ou footer */

O :where() funciona igual em comportamento — mas tem especificidade zero. Sempre. Não importa o que você colocar dentro.

TEXT
/* :where() — especificidade zero */:where(#header, .nav, footer) a {  color: blue;}/* ↑ fácil de sobrescrever — qualquer seletor   de classe ou tag já ganha */

Na prática isso significa que :where() é ideal pra estilos base e resets — você define sem travar o desenvolvedor que vai customizar depois.

A diferença na vida real

Problema com :is()
TEXT
/* base.css */:is(article, section) p {  color: gray;}/* theme.css — não vai funcionar */p {  color: black;}/* :is(article, section) p tem   especificidade maior que p */
Solução com :where()
TEXT
/* base.css */:where(article, section) p {  color: gray;}/* theme.css — funciona */p {  color: black;}/* :where() tem especificidade zero   qualquer p ganha na sobreposição */

Um exemplo que junta os dois

Num design system, você usaria os dois em camadas diferentes:

TEXT
/* Camada base — :where() pra não travar ninguém */:where(h1, h2, h3, h4, h5, h6) {  margin: 0 0 0.5em;  line-height: 1.2;}/* Camada de componente — :is() pra contextos específicos */.card :is(h2, h3) {  font-size: 1.25rem;  color: var(--color-heading);}/* Estados interativos — :is() porque precisa de peso */.btn:is(:hover, :focus-visible) {  outline: 2px solid currentColor;  outline-offset: 2px;}