Cascade Layers do CSS: Como Dominar a Especificidade Sem Gambiarras
O problema que !important nunca resolveu
Especificidade no CSS funciona como um sistema de pontuação: IDs vencem classes, classes vencem elementos, inline vence tudo. Quando dois seletores competem, o mais específico ganha. O mecanismo é previsível, mas a previsibilidade se perde quando o projeto cresce e múltiplas origens de estilo colidem: reset global, design system, estilos de componente, overrides de página, CSS de terceiros.
A reação instintiva é escalar especificidade. Adicionar um ID. Empilhar classes. Quando nada funciona, !important. E quando dois !important colidem, a especificidade volta a decidir, só que agora dentro de uma dimensão mais difícil de depurar.
Cascade Layers (@layer) atacam esse problema em uma camada acima da especificidade. Literalmente. Elas criam um nível de prioridade na cascata que é avaliado antes da especificidade dos seletores. Isso significa que um seletor de elemento simples (p) em uma camada de alta prioridade vence um seletor de ID (#hero .title) em uma camada de baixa prioridade, sem nenhum !important.
Como a cascata funciona com layers
A cascata do CSS resolve conflitos nesta ordem (simplificada):
- Origem e
!important(user-agent, user, author) - Contexto (inline style, Shadow DOM)
- Camada (layer) ← novo nível
- Especificidade do seletor
- Ordem de aparição no código
Layers se encaixam entre contexto e especificidade. Quando dois seletores estão em camadas diferentes, a camada de maior prioridade vence, independentemente de quantos IDs o seletor perdedor tenha.
A ordem de prioridade das camadas é definida pela ordem de declaração: a última camada declarada tem maior prioridade. E estilos fora de qualquer camada (unlayered) vencem todas as camadas.
/* A ordem aqui define prioridade: reset < base < components < utilities */
/* utilities vence components, que vence base, que vence reset */
@layer reset, base, components, utilities;
Declarando e usando camadas
Existem três formas de criar e popular uma camada.
Declaração antecipada com preenchimento posterior
/* Declara a ordem de prioridade no topo do arquivo principal */
@layer reset, base, components, utilities;
/* Preenche cada camada onde quiser, inclusive em arquivos separados */
@layer reset {
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
}
@layer base {
body {
font-family: "Inter", system-ui, sans-serif;
line-height: 1.6;
color: #1a1a1a;
}
/* Seletor simples de elemento: funciona porque a camada controla prioridade */
a {
color: #2563eb;
text-decoration-thickness: 1.5px;
}
}
Declaração inline (camada criada e preenchida ao mesmo tempo)
@layer components {
.card {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 1.5rem;
background: #fff;
}
.card-header {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.75rem;
}
}
Importação direta em uma camada
/* CSS de terceiro entra na camada de menor prioridade */
/* Qualquer estilo seu em camadas posteriores vence automaticamente */
@import url("vendor/datepicker.css") layer(vendor);
@layer vendor, reset, base, components, utilities;
Essa terceira forma é a mais poderosa para lidar com CSS de bibliotecas externas. Todo o CSS importado fica confinado na camada vendor, e qualquer estilo seu em components ou utilities vence sem precisar inflar seletores.
Arquitetura de camadas para projetos reais
Uma estrutura que funciona para a maioria dos projetos frontend com design system:
/* styles/layers.css */
/* Arquivo de entrada: define a ordem canônica de camadas */
@import url("./vendor.css") layer(vendor);
@layer vendor, reset, tokens, base, layout, components, overrides, utilities;
| Camada | Responsabilidade | Exemplo de conteúdo |
|---|---|---|
vendor | CSS de terceiros (datepickers, editores rich text) | datepicker.css, |
Leia o artigo completo em https://www.vivodecodigo.com.br/react/css-cascade-layers-especificidade-sem-gambiarras