Curso de CSS / Herança, especificidade e cascata

A cascata escolhe entre declarações CSS concorrentes, enquanto a especificidade compara a força dos seletores dentro das condições relevantes da cascata. A herança fornece certos valores de um elemento pai quando nenhuma declaração aplicável os determina; estes são mecanismos relacionados, mas diferentes.

Distinga herança de declarações diretas

Herança é quando um elemento filho herda o valor de seu elemento pai. Enquanto a cor do texto e propriedades relacionadas à fonte são geralmente herdadas, borda e padding não são herdados por padrão. Se você assumir que todas as propriedades serão iguais às do pai, pode acabar encontrando bordas inesperadas em seus parágrafos.

#main {
  color: purple;
  border: 1px solid purple;
}
p { color: #555; }
<main id="main">
  <h1>Small Garden</h1>
  <p>Visitor information.</p>
</main>

Assumindo que aplicamos apenas essas duas peças, h1 herdará a cor roxa de seu pai main. A cor cinza que você especifica é aplicada ao p. Só porque o #main do pai é um seletor de ID, isso não prevalece sobre a atribuição direta do p filho. Isso ocorre porque não há uma competição entre dois seletores aplicando-se ao mesmo elemento. A borda é criada ao redor da caixa de main e não é duplicada pelas bordas de h1 e p, respectivamente.

Propriedades não herdadas também podem herdar o valor calculado do pai especificando-o como border: inherit. Por outro lado, mesmo que haja uma declaração que se aplique ao elemento, mesmo que seja uma propriedade herdada, essa declaração é processada primeiro. Você pode descobrir quais propriedades são herdadas e o que herdar significa na descrição de herança de CSS da MDN.

Siga o processo de decisão da cascata

Comece com o CSS do autor comum, como as regras que você escreve para esta página. Antes de comparar declarações, verifique se o seletor corresponde, se a sintaxe é válida e se condições como consultas de mídia se aplicam. Exclua regras não correspondentes e declarações inválidas da comparação.

  1. Verifique as principais condições, como origem, importância e camada. Origem CSS do autor e CSS do usuário/padrão do navegador, se é !important, se @layer é usado, etc. são relevantes aqui. Se as principais condições forem diferentes, a decisão não é tomada apenas comparando os valores dos seletores.
  2. Compare a especificidade do seletor dentro das mesmas condições. Prioridade (especificidade) é um critério para comparar quão específico um seletor é. Compare ID, família de classes e família de elementos nessa ordem.
  3. Se a prioridade for a mesma, a declaração escrita por último é utilizada. Em situações onde não há condições adicionais, como @scope, como nos exemplos desta lição, os empates finais são resolvidos pela ordem de origem.
  4. Para cada propriedade, o CSS resolve as declarações aplicáveis e então realiza o preenchimento padrão quando necessário. Propriedades herdáveis podem obter um valor do elemento pai; outras propriedades normalmente usam seu valor inicial. Isso não significa que toda declaração em uma regra vencedora substitua uma regra anterior inteira.

A cascata é o processo completo de resolução de conflitos do CSS; a especificidade do seletor é um critério dentro dele. Animações, transições, camadas e escopos introduzem regras adicionais, portanto, os exemplos simples aqui não representam todos os casos. As regras de ordenação da cascata do W3C definem os relacionamentos entre origem, importância e camadas. O exemplo completo abaixo não utiliza animações, transições, @layer ou @scope. O artigo Organize o CSS com Estilos Compartilhados, Aninhamento e Camadas demonstra como agrupar regras em camadas.

Compare a especificidade em três colunas

O seletor básico nesta lição pode ser lido em três colunas: número de IDs, número de classes/atributos/pseudo-classes e número de elementos/pseudo-elementos. Compare da coluna da esquerda e, se houver diferença, decida com base no resultado. Não memorize apenas como uma tabela de pontuação decimal somando ID=100, classe=10, elemento=1. Só porque há muitas classes não significa que ultrapassa a coluna de ID.

seletor ID Classe, atributo, pseudo-classe Elemento/Pseudo-elemento notação
p 0 0 1 0-0-1
.notice 0 1 0 0-1-0
p.notice 0 1 1 0-1-1
[required] 0 1 0 0-1-0
a:hover 0 1 1 0-1-1
p::before 0 0 2 0-0-2
#opening 1 0 0 1-0-0
#main .notice 1 1 0 1-1-0

p.notice e .notice têm a mesma coluna de ID e a mesma coluna de classe. Há uma diferença entre 1 e 0 na coluna de elemento, então p.notice tem maior especificidade. #opening e p.notice têm uma diferença de 1 e 0 na primeira coluna, então #opening tem maior especificidade. Espaços em branco, notações relacionais como >, +, ~ e o seletor universal * não aumentam diretamente o número dessas comparações.

p.notice { color: #087f5b; }
.notice { color: tomato; }

Sob as mesmas condições de origem, importância e camada, p.notice tem maior especificidade que .notice, então sua cor verde vence mesmo quando aparece antes. Seletores idênticos têm especificidade igual, permitindo que a ordem de origem resolva um empate. Algumas pseudo-classes funcionais têm regras adicionais de especificidade. Veja MDN: Especificidade.

Resolva cada propriedade separadamente

.notice {
  color: blue;
  padding: 12px;
  border: 1px solid #087f5b;
}
.notice {
  color: tomato;
  padding: 20px;
}

Se apenas essas duas regras forem aplicadas ao mesmo elemento, cor e padding usam a declaração da última, e a borda da regra anterior permanece. Só porque não há borda na regra seguinte não significa que toda a regra anterior desaparece. O CSS deve ser entendido como “determinando valores para cada propriedade” em vez de “selecionando um bloco de regra inteiro”.

Por exemplo, a cor do texto pode vir de uma regra de ID, a margem pode vir de uma regra de classe posterior, e a borda pode vir de uma regra de classe anterior. Mesmo quando você vê o texto riscado nas ferramentas de desenvolvimento, leia as declarações uma a uma. Só porque a cor está riscada não significa que o padding do mesmo bloco não está aplicado.

Preveja o resultado do exemplo completo antes de executá-lo

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Small Garden</title>
  <style>
body { margin: 24px; font-family: sans-serif; line-height: 1.7; }
    #main { color: purple; }
    p { color: #555; }
    .notice {
      color: blue;
      padding: 12px;
      border: 1px solid #087f5b;
    }
    p.notice { color: #087f5b; }
    .notice { color: tomato; padding: 20px; }
    #opening { color: #1d4ed8; }
  </style>
</head>
<body>
<main id="main">
    <h1>Small Garden</h1>
    <p id="opening" class="notice">A: We open at 9am every day.</p>
    <p class="notice">B: Prepare a water bottle and comfortable shoes.</p>
    <p>C: Please inquire at the information desk.</p>
  </main>
</body>
</html>

Preveja as cores de A·B·C e o espaçamento interno do aviso antes de abri-lo em seu navegador. Se você salvar todo o código como index.html e abrir, você obterá o seguinte resultado. A fonte do body e a altura da linha são configurações auxiliares para facilitar a visualização.

objeto e propriedades resultado final Motivo da decisão
cor do h1 Roxo Herdou a cor do main. Não há outras declarações de cor no h1.
cor de A Azul #1d4ed8 #opening tem especificidade maior que p.notice, .notice, p aplicados ao mesmo A.
cor de B Verde #087f5b p.notice tem maior especificidade que a regra .notice escrita depois.
cor de C Cinza #555 Usa um valor direto para p e não herda a cor roxa do pai.
preenchimento de A·B 20px As duas declarações .notice têm a mesma especificidade, então o preenchimento da última prevalece.
borda de A·B linha sólida verde de 1px Como não há declaração de borda na última regra, a declaração válida anterior permanece.

Quando verificado em Computed, a cor de A pode ser exibida como rgb(29, 78, 216), a cor de B pode ser exibida como rgb(8, 127, 91), e a cor de C pode ser exibida como rgb(85, 85, 85). Mesmo que a notação da cor seja diferente, indica o mesmo valor. Verifique se o preenchimento é 20px em cada direção e se a espessura e o formato da linha da borda são os mesmos.

Compare !important e estilos inline

!important é uma notação que altera a importância de uma declaração. Não é uma função que adiciona um grande número às três colunas do seletor. Na origem de autor, nas condições deste exemplo, declarações importantes têm precedência sobre declarações comuns, então adicionar a regra abaixo ao final de todo o exemplo mudará a cor de A e B para tomato (vermelho alaranjado). A declaração de cor simples no ID também não tem precedência sobre esta declaração importante.

.notice { color: tomato !important; }

Este resultado não significa que important é sempre mais forte do que todos os estilos. Estilos importantes do usuário, valores durante transições, etc., seguem uma ordem separada. Mesmo entre declarações importantes, você deve primeiro verificar a origem e a camada. Neste exercício, observe o important e depois exclua a regra que você adicionou. Normalmente, é melhor para manutenção organizar primeiro o intervalo de seleção e a estrutura.

<p class="notice" style="color: purple;">B: Information on what to prepare</p>

No exemplo original onde a regra important foi deletada, se você colocar uma cor inline em B como acima, ela terá precedência sobre as regras regulares da folha de estilo e B se tornará roxo. Estilos inline são tratados separadamente das três colunas de seletores padrão na tabela acima. Novamente, colocar important na regra da classe geral permite que ela tenha precedência sobre a cor inline regular. Portanto, “se for inline, é incondicionalmente o mais forte” também é uma explicação sem condições.

Pratique diagnosticar conflitos

  1. No exemplo completo original, mova a regra p.notice para o final. Certifique-se de que B ainda esteja verde e que A mantenha a cor azul do seu ID. Em casos com alta especificidade, elas não serão anuladas simplesmente alterando sua ordem.
  2. Em seguida, altere apenas o seletor p.notice para .notice e coloque-o na parte inferior. A cor de B usa o verde especificado pelo último .notice. Como temos o mesmo seletor, a ordem agora é o critério de decisão. Após a confirmação, restaure o original.
  3. Remova apenas id="opening" de A. Veja se A se torna do mesmo verde que B. Mesmo que o ID do elemento principal permaneça o mesmo, ele não supera a cor verde atribuída diretamente a A. Após conferir o resultado, restaure o ID de A.
  4. Em etapas separadas, aplique os exemplos de !important e estilos inline e compare os resultados. Volte ao original entre os testes para não deixar regras anteriores interferirem na condição seguinte.

Se o resultado difere da sua expectativa, Inspecione o CSS com DevTools: Styles e Computed. Selecione o elemento correto, encontre as declarações da propriedade, compare sua importância, seletores e ordem, e então inspecione o valor final. Adicionar IDs ou !important sem encontrar a causa pode alterar a cor agora enquanto torna edições futuras mais difíceis.