VocĂȘ escreve .sidebar .card { color: blue; }, carrega a pĂĄgina e o card continua vermelho por causa de uma regra adicionada uma hora antes: .card { color: red; }. A nova regra vem depois no arquivo. Mesmo assim, perde. O navegador compara os dois seletores com um sistema de pontos fixo, e .sidebar .card pontua mais alto que .card, nĂŁo importa qual vocĂȘ escreveu por Ășltimo.

Esse sistema de pontos Ă© a especificidade. Depois que vocĂȘ aprende a calculĂĄ-la de cabeça, “por que meu CSS nĂŁo se aplica” se torna aritmĂ©tica pura. NĂŁo tem nada a ver com layout: Grid ou Flexbox decide onde um elemento fica, a especificidade decide qual regra define a cor ou o tamanho quando duas regras entram em conflito.

Como o navegador pontua um seletor

Cada seletor tem um peso formado por trĂȘs nĂșmeros, geralmente escritos como um trio separado por hĂ­fens: ID-classe-tipo. Some o que o seletor contĂ©m, coluna por coluna:

ColunaO que contaPeso por ocorrĂȘncia
ID#header1-0-0
Classe / atributo / pseudoclasse.card, [type="radio"], :hover, :nth-child(2)0-1-0
Tipo / pseudoelementodiv, h1, ::before0-0-1

O seletor universal * e os combinadores (o espaço, >, ~, +) somam 0-0-0. Eles decidem quais elementos correspondem, não com que força a regra vence.

Alguns seletores reais:

1
2
3
4
5
6
.card            { }  /* 0-1-0 */
.sidebar .card   { }  /* 0-2-0 */
#main .card      { }  /* 1-1-0 */
div.card         { }  /* 0-1-1 */
a:hover          { }  /* 0-1-1 */
nav ul li a      { }  /* 0-0-4 */

Para comparar dois valores, vå da esquerda para a direita; a primeira coluna que divergir decide. 1-1-0 vence 0-2-0 porque a coluna do ID (1 contra 0) decide antes de alguém olhar para as classes. Assim, #main .card vence .sidebar .card .title .text, não importa quão longo seja o segundo seletor. Um ID supera qualquer quantidade de classes.

Por que uma regra CSS mais recente pode continuar perdendo

Voltando ao exemplo inicial, com os nĂșmeros calculados:

1
2
.card        { color: red; }   /* 0-1-0 */
.sidebar .card { color: blue; } /* 0-2-0 */

0-2-0 vence 0-1-0 na coluna das classes, entĂŁo o card fica azul independentemente da ordem no arquivo. A ordem no arquivo Ă© o critĂ©rio ao qual quase todo mundo recorre primeiro, mas sĂł vale quando os trios sĂŁo idĂȘnticos:

1
2
.card { color: red; }   /* 0-1-0 */
.item { color: blue; }  /* 0-1-0, mesmo peso, declarada depois: vence */

As duas regras pontuam 0-1-0, entĂŁo vence a que aparece mais tarde na folha de estilo (ou no bloco <style> posterior, ou na folha vinculada depois da outra). Esse Ă© o Ășnico caso em que “a Ășltima vence” Ă© o modelo mental correto. Funciona em projetos pequenos, por isso Ă© a primeira coisa que todo mundo aprende, e para de funcionar assim que um projeto passa de um punhado de seletores.

Os estilos inline ficam acima de tudo isso. Um atributo style="color: green" no elemento vence qualquer regra em qualquer folha de estilo, não importa quantos IDs ela encadeie. Só as declaraçÔes !important conseguem superå-lo.

1
<p class="card" id="warning" style="color: green;">...</p>
1
2
#warning { color: red; }         /* perde para o estilo inline */
#warning { color: orange !important; } /* vence — !important tambĂ©m supera estilos inline */

!important nĂŁo faz parte do trio de especificidade. É um nĂ­vel separado, de prioridade mais alta: qualquer declaração !important vence qualquer declaração normal, estilos inline incluĂ­dos, independentemente do peso do seletor. Quando duas declaraçÔes !important entram em conflito, a especificidade decide (depois a ordem no arquivo), exatamente como sem a flag. É assim que um Ășnico !important se transforma numa corrida armamentista. O prĂłximo desenvolvedor que precisar sobrescrevĂȘ-lo nĂŁo tem outra jogada alĂ©m de outro !important, e logo ninguĂ©m sabe mais qual regra deveria vencer.

Quais seletores somam zero especificidade: :where() contra :is()

Alguns seletores permitem direcionar com precisão sem pagar o preço normal.

:where() sempre contribui com 0-0-0, independentemente do que vocĂȘ colocar dentro:

1
:where(.theme-dark, .theme-light) .card { background: #222; } /* 0-1-0 — só .card conta */

Isso torna :where() o invólucro certo para valores padrão e estilos de tema que precisam ser fåceis de sobrescrever. Uma biblioteca de componentes pode entregar seus padrÔes dentro de :where(), e uma regra simples .card { } de quem a usa vence sem precisar de !important.

:is(), :not() e :has() funcionam de forma diferente. NĂŁo somam peso prĂłprio, mas assumem o peso do seu argumento mais especĂ­fico:

1
2
3
p                 { }  /* 0-0-1 */
:is(p, #intro)    { }  /* 1-0-0 — #intro Ă© o argumento mais pesado */
div:not(.hidden)  { }  /* 0-1-1 — .hidden ainda conta */

EntĂŁo :where() Ă© gratuito, e :is() custa tanto quanto o mais pesado que vocĂȘ colocar dentro, mesmo que esse argumento nunca corresponda ao elemento.

Como as cascade layers sobrepÔem a especificidade

Se vocĂȘ estĂĄ acumulando classes e IDs para ganhar disputas que nĂŁo tĂȘm nada a ver com o quĂŁo especĂ­fico um seletor deveria ser, o projeto precisa de @layer, nĂŁo de mais aninhamento.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@layer reset, base, components, utilities;

@layer reset {
  * { margin: 0; padding: 0; }
}

@layer components {
  .card { color: red; }
}

@layer utilities {
  .text-blue { color: blue; }
}

A ordem das layers vem de onde vocĂȘ declara os nomes, nĂŁo do peso dos seletores. utilities foi declarada por Ășltimo, entĂŁo uma regra dentro dela vence uma regra em components, mesmo que .text-blue tenha o mesmo peso 0-1-0 que .card. A especificidade continua resolvendo conflitos dentro de uma layer. Entre layers, a ordem das layers vence sem apelação e a especificidade nunca Ă© consultada.

Uma regra pega quase todo mundo de surpresa na primeira vez: para declaraçÔes normais (sem !important), estilos fora de qualquer layer vencem estilos dentro de uma layer. VocĂȘ move seu CSS de componentes para @layer components mas deixa uma sobrescrita sem layer, e essa regra sem layer vence independentemente da ordem em que vocĂȘ declarou as layers. Para declaraçÔes !important, a precedĂȘncia se inverte: as regras important da layer declarada primeiro vencem as das layers posteriores, e os estilos important sem layer ficam no Ășltimo lugar de todos. A inversĂŁo Ă© intencional, e Ă© a parte da especificação que vale reler antes de depender dela.

O suporte dos navegadores jĂĄ deixou de ser um argumento contra as layers hĂĄ um tempo: @layer roda em todos os engines principais desde 2022. O custo real Ă© organizacional. O time precisa combinar uma ordem de layers antecipadamente (reset, base, components, utilities Ă© um esquema comum) e encaixar o CSS novo na layer certa. É mais um problema de processo do que de sintaxe.

Erros comuns com especificidade CSS

  • Combater especificidade com mais especificidade. Cada sobrescrita adiciona uma classe ou um ID mais pesado que a anterior, atĂ© mudar uma cor exigir #app .page .section .card.is-active.is-selected. É o mesmo padrĂŁo de falha do spaghetti code: cada correção adiciona mais confusĂŁo em vez de remover. A solução quase nunca Ă© um seletor mais pesado. É um mais plano, ou uma layer.
  • Recorrer a !important no primeiro conflito. Ganha a disputa imediata e prepara a prĂłxima, porque a prĂłxima sobrescrita nĂŁo tem mais nenhuma jogada de especificidade normal disponĂ­vel.
  • Esquecer que pseudoclasses contam. :hover, :focus, :nth-child() e o argumento de :not() somam todos 0-1-0. Um simples a { color: blue; } mais abaixo no arquivo nĂŁo vai sobrescrever a:not(.disabled) { color: red; } escrito acima, porque a versĂŁo com :not() Ă© mais pesada.
  • Assumir que o valor herdado vence porque a regra “parece” mais forte. Uma regra que aponta diretamente para um elemento sempre vence um valor herdado, qualquer que seja a especificidade da regra de onde vem a herança. Se um filho mostra a cor errada, procure uma regra que aponte para esse filho antes de suspeitar da herança.
  • Misturar :is() num seletor esperando que continue barato. :is() assume o peso do seu argumento mais pesado, entĂŁo :is(.card, #sidebar) Ă© na prĂĄtica um seletor 1-0-0 disfarçado.

Como verificar a especificidade no DevTools

VocĂȘ nĂŁo precisa contar colunas na mĂŁo quando um bug estĂĄ na sua frente. Inspecione o elemento no Chrome ou Firefox DevTools e abra o painel Styles (Rules no Firefox). Cada regra correspondente aparece listada em ordem de cascata, com as declaraçÔes sobrescritas tachadas. Passe o mouse sobre um seletor e os dois navegadores mostram sua especificidade. A partir daĂ­ vocĂȘ sabe em segundos se estĂĄ diante de uma diferença de especificidade, um problema de ordem de layers, ou simples ordem de arquivo entre duas regras de peso igual.

Na próxima vez que uma regra perder sem motivo aparente, calcule os dois trios antes de adicionar uma classe ou um !important para forçå-la. Normalmente, a correção certa é tirar peso do seletor que estå vencendo, não empilhar mais peso no que deveria ter vencido.

Artigos relacionados