Especificidade, cascade e @layer
TL;DR
Quando múltiplas regras CSS tentam definir o mesmo valor para o mesmo elemento, o algoritmo de cascade decide qual vence — avaliando na ordem: origem + importância → especificidade → posição no código.
@layeré a ferramenta moderna que resolve wars de especificidade sem!important: você declara uma hierarquia explícita de camadas e regras em camadas superiores sempre vencem, independente da especificidade dos seletores.
O algoritmo de cascade — completo
A nota 01 apresentou a cascade em alto nível. Aqui, o algoritmo completo, na ordem de prioridade decrescente:
flowchart TD A["1. Transições CSS\n(animation-fill / transition em andamento)"] B["2. !important do browser\n(acessibilidade, modo forçado)"] C["3. !important do usuário\n(high contrast, preferências)"] D["4. !important do autor\n(seu CSS com !important)"] E["5. Animações CSS\n(@keyframes em execução)"] F["6. CSS normal do autor\n(maior especificidade vence)"] G["7. CSS normal do usuário"] H["8. CSS normal do browser\n(user-agent stylesheet)"] A --> B --> C --> D --> E --> F --> G --> H style A fill:#ff9999 style D fill:#ffcc99 style F fill:#99ff99
Na prática do dia a dia: a maioria das decisões acontece no nível 6 — CSS normal do autor, resolvido por especificidade.
Especificidade em profundidade
Especificidade é calculada como um trio (A, B, C):
| Componente | O que conta | Valor |
|---|---|---|
| A | Seletores de ID | #header = (1, 0, 0) |
| B | Classes, pseudo-classes, atributos | .btn, :hover, [type] = (0, 1, 0) |
| C | Elementos, pseudo-elements | div, ::before = (0, 0, 1) |
Comparação: da esquerda para a direita. (1, 0, 0) > (0, 99, 0). Um ID sempre vence, não importa quantas classes o rival tenha.
/* Calculando especificidade */
div /* (0, 0, 1) */
.btn /* (0, 1, 0) */
.btn:hover /* (0, 2, 0) — :hover é pseudo-classe */
div.btn /* (0, 1, 1) */
#header /* (1, 0, 0) */
#header .nav /* (1, 1, 0) */
#header .nav > a /* (1, 1, 1) */
/* Pseudo-elements */
::before /* (0, 0, 1) — como elemento */
.btn::before /* (0, 1, 1) */Atributos style inline
Atributos de estilo inline têm especificidade ainda maior — (1, 0, 0, 0) em um esquema de 4 componentes — acima de qualquer seletor de ID:
<!-- Este color: red VENCE qualquer seletor no stylesheet -->
<p style="color: red">Texto</p>O papel de :is(), :not(), :has() na especificidade
/* :is() — a especificidade é do seletor mais específico dentro */
:is(h1, h2, .titulo) { color: blue; }
/* Especificidade: (0, 1, 0) — porque .titulo é mais específico */
/* :not() — mesma regra: especificidade do argumento */
:not(#header) { color: blue; }
/* Especificidade: (1, 0, 0) — porque #header é um ID */
/* :where() — especificidade ZERO, sempre */
:where(h1, h2, .titulo) { color: blue; }
/* Especificidade: (0, 0, 0) — ideal para resets e base styles */
/* :has() — especificidade do seletor dentro */
.card:has(img) { padding: 0; }
/* Especificidade: (0, 2, 0) — .card (0,1,0) + :has(img) conta img=(0,0,1)? */
/* Na prática: :has() contribui com a especificidade do seletor dentro */
:where()em reset e base stylesPor ter especificidade zero,
:where()é ideal para estilos base que devem ser fáceis de sobrescrever. Nenhum seletor específico do autor precisa lutar contra ela.
!important — quando e por que evitar
!important eleva uma declaração acima de todas as declarações normais do autor. Vence especificidade. Mas:
- Torna o código difícil de manter — para sobrescrever
!important, você precisa de outro!important - Cria “wars de importante” — equipes adicionando
!importantem cima de!important - Sinal de design ruim de especificidade
/* ❌ !important como muleta */
.modal button { background: blue !important; }
.theme-dark .modal button { background: darkblue !important; } /* guerra */
/* ✅ Resolver a raiz do problema */
.theme-dark .modal button { background: darkblue; }
/* Especificidade (0, 3, 1) > (0, 2, 1) — vence sem !important */Uso legítimo de !important: estilos de utilitário que devem sempre vencer, como classes .sr-only (screen-reader only) ou classes de debug. E com @layer, você raramente precisa dele.
BEM — nomenclatura para controlar especificidade
Antes de @layer, a solução mais adotada era BEM (Block Element Modifier): uma convenção de nomenclatura que mantém seletores em baixa especificidade:
/* Block */
.card { }
/* Element (filho do block) */
.card__title { }
.card__content { }
.card__footer { }
/* Modifier (variação) */
.card--featured { }
.card--compact { }
.card__title--large { }Todos os seletores BEM têm especificidade (0, 1, 0) — uma classe. Não há conflito de especificidade porque cada elemento tem uma classe única. A desvantagem é o HTML verboso e o naming estressante.
@layer — o mecanismo moderno
@layer resolve wars de especificidade definitivamente: você declara uma hierarquia de camadas, e regras em uma camada superior sempre vencem regras de camadas inferiores, independente da especificidade dos seletores.
Declaração e ordem
/* Declarar a ordem no início — layers posteriores têm mais peso */
@layer reset, base, components, utilities;
/* A ordem de DECLARAÇÃO define a hierarquia, não onde as regras estão */
@layer reset {
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
}
@layer base {
body { font-family: system-ui, sans-serif; }
h1 { font-size: 2rem; }
}
@layer components {
.btn {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
}
.btn--primary { background: blue; color: white; }
}
@layer utilities {
.mt-4 { margin-top: 1rem; }
.text-center { text-align: center; }
}Resultado: utilities > components > base > reset — mesmo que .btn tenha especificidade (0, 1, 0) e .mt-4 também. A camada utilities vence pela posição na hierarquia.
Regras fora de layers
Estilos sem @layer têm prioridade acima de todos os layers:
@layer components {
.btn { color: blue; }
}
/* Esta regra vence — está fora de qualquer layer */
.btn { color: red; }Use isso para override de emergência sem quebrar a arquitetura de layers.
!important dentro de layers — a inversão
Dentro de layers, !important inverte a hierarquia:
@layer reset, base, utilities;
@layer reset {
p { color: black !important; } /* !important em reset */
}
@layer utilities {
.text-red { color: red; } /* sem !important */
}
p.text-red { /* qual vence? */ }
/* Color: black — !important em reset vence utilities normais */
/* (layers !important: reset > base > utilities — ordem invertida) */Esse comportamento raramente é necessário na prática. A regra: use !important dentro de layers só em casos extremos, como forçar acessibilidade.
Importar CSS em uma layer
/* Bootstrap em uma layer — suas classes não vencerão as suas */
@layer external {
@import url('bootstrap.min.css');
}
/* Suas classes vencem automaticamente por estar fora de layer */
.btn { /* sempre vence .btn do Bootstrap */ }Esse padrão é muito útil para isolar CSS de terceiros.
Layers anônimos
/* Layer sem nome — não pode ser referenciado depois */
@layer {
.reset { margin: 0; }
}Útil para encapsular estilos que nunca precisam de override.
Ordem de cascade: o quadro completo
flowchart TD subgraph Autor["CSS do Autor"] direction TB U["!important fora de layer"] L4["!important @layer reset\n(menor prioridade dentro de !important)"] L3["!important @layer base"] L2["!important @layer components"] L1["!important @layer utilities\n(maior prioridade dentro de !important)"] NL["Normal fora de layer\n(maior prioridade sem !important)"] LA["Normal @layer utilities"] LB["Normal @layer components"] LC["Normal @layer base"] LD["Normal @layer reset\n(menor prioridade)"] U --> L4 --> L3 --> L2 --> L1 NL --> LA --> LB --> LC --> LD end
Herança e all
A propriedade all aplica um valor a todas as propriedades de um elemento:
/* Remove todos os estilos (inclusive herança) e usa os do user-agent */
.isolado { all: revert; }
/* Remove todos os estilos e usa initial de cada propriedade */
.reset-total { all: initial; }
/* Herda tudo do pai explicitamente */
.herdar-tudo { all: inherit; }
/* Mesmo que initial, mas mantém as animações */
.reset-sem-animation { all: unset; }Anti-padrões e soluções
/* ❌ Seletores super específicos — difíceis de sobrescrever */
body > main > section > div.card > .title { color: blue; }
/* ✅ Uma classe focada */
.card__title { color: blue; }
/* ❌ !important em cascata */
.title { color: blue !important; }
.dark .title { color: white !important; }
.modal.dark .title { color: lightgray !important; } /* escala para sempre */
/* ✅ @layer resolve */
@layer base { .title { color: blue; } }
@layer themes { .dark .title { color: white; } }
@layer contexts { .modal.dark .title { color: lightgray; } }
/* ❌ ID em CSS de componente — muda especificidade do sistema inteiro */
#btn-principal { background: blue; }
/* ✅ Classe — mesmo resultado, especificidade controlada */
.btn--principal { background: blue; }Para fixar
- Um seletor
#header .nav ae outro.nav li a.active— qual tem maior especificidade? Calcule.- Qual é a especificidade de
:is(h1, .titulo, #destaque)? E de:where(h1, .titulo, #destaque)?- Você declara
@layer reset, utilities. Uma regra emresettem um seletor com 3 IDs. Uma regra emutilitiestem um seletor de uma classe. Qual vence?- O que acontece com
!importantdentro de layers? Por que a ordem é invertida?- CSS de um framework externo está sobrescrevendo suas classes. Você tem controle sobre o HTML mas não pode modificar o framework. Como você resolveria com
@layer?- Por que
:where()é preferível a seletores diretos em estilos de reset?
Veja também
- 04 — CSS Grid — anterior
- 06 — Design responsivo — próxima
- 01 — Modelo mental — cascade introdutório
- 08 — Seletores modernos —
:is(),:where(),:has()em profundidade