O que é CSS e como o navegador aplica estilo na página
CSS é a camada que decide como o HTML aparece: o que é uma regra, como o navegador casa seletor com elemento e por que a ordem importa.
CSS é a linguagem que descreve como o HTML deve aparecer. O HTML diz que aquele texto é um título; o CSS diz que o título é verde e tem letra de 32 pixels. O navegador lê as duas descrições, combina uma com a outra e só então pinta a tela.
Os exemplos daqui montam a página da Clínica Veterinária Pata Firme, uma clínica de bairro que atende cachorro e gato e ainda não tem site. Se você nunca escreveu uma tag, comece por o que é HTML e volte depois.
Uma observação sobre as saídas: nada aqui foi digitado à mão, e o que aparece
logo abaixo de cada bloco é o que o console imprimiu. A maioria dos blocos js
é código de navegador, executado num Chromium 151 sem interface gráfica — esses
você cola no console do DevTools e vê os mesmos números. Os três que começam com
import são a exceção: rodam no Node, pelo terminal, e o texto avisa quando é o
caso.
A mesma página com a folha ligada e desligada
Este é o HTML da clínica. Repare na linha do link: é ela que traz o CSS para
dentro da página.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Clínica Veterinária Pata Firme</title>
<link rel="stylesheet" href="estilo.css">
</head>
<body>
<h1>Clínica Veterinária Pata Firme</h1>
<p class="aviso">Atendemos sem hora marcada aos sábados.</p>
<ul>
<li>Consulta clínica — R$ 120</li>
<li>Vacina antirrábica — R$ 90</li>
<li>Banho e tosa — R$ 70</li>
</ul>
</body>
</html>E este é o estilo.css, com três regras curtas:
body {
background-color: #f4f1ea;
color: #2b2b2b;
}
h1 {
color: #1f6f4a;
font-size: 32px;
}
.aviso {
background-color: #fff3cd;
padding: 12px;
}Em vez de acreditar no efeito, dá para perguntar ao navegador. A função
getComputedStyle devolve o valor final que ele decidiu usar em cada
propriedade, depois de somar tudo. Desligando a folha com
document.styleSheets[0].disabled = true, a mesma medição roda de novo sem o
nosso CSS:
const v = (sel, prop) => getComputedStyle(document.querySelector(sel))[prop];
const ler = () => [
`body fundo: ${v('body', 'background-color')} · texto: ${v('body', 'color')}`,
`h1 cor: ${v('h1', 'color')} · tamanho: ${v('h1', 'font-size')}`,
`.aviso fundo: ${v('.aviso', 'background-color')} · padding: ${v('.aviso', 'padding')}`,
].join('\n');
console.log('--- com estilo.css ---\n' + ler());
document.styleSheets[0].disabled = true;
console.log('--- com a folha desligada ---\n' + ler());Três coisas para anotar. Primeiro: o navegador não devolveu #f4f1ea, devolveu
rgb(244, 241, 234). Ele normaliza todo valor de cor, então não se assuste ao
ver a sua notação trocada no DevTools. Segundo: fundo “sem cor” não é branco, é
rgba(0, 0, 0, 0) — transparente. O branco que você enxerga é a janela do
navegador atrás da página.
E o terceiro é o mais interessante: o h1 continuou com 32px mesmo sem a nossa
folha. A regra font-size: 32px não mudou nada; ela só repetiu um tamanho que
o navegador já tinha decidido sozinho. Guarde essa pista, porque ela reaparece
três seções abaixo.
Anatomia de uma regra: seletor, propriedade e valor
Toda a linguagem cabe num desenho. Uma regra tem um seletor, que diz para quem ela vale, e um bloco de declarações entre chaves. Cada declaração é um par de propriedade e valor, fechado com ponto e vírgula.
Essa separação não é didática, é literal: um analisador de CSS separa exatamente
essas peças. O css-tree é um desses analisadores, escrito em JavaScript — e é
o primeiro dos três blocos que não rodam no navegador: ele é um pacote de
terminal, instalado numa pasta ao lado do estilo.css.
npm init -y
npm install css-treeCom ele instalado, dá para pedir que abra o nosso arquivo e mostre o que
enxergou. Salve o trecho abaixo como anatomia.mjs (a extensão .mjs é o que
libera o import) e rode com node anatomia.mjs:
import * as csstree from 'css-tree';
import fs from 'node:fs';
const arvore = csstree.parse(fs.readFileSync('estilo.css', 'utf8'));
let regras = 0;
let declaracoes = 0;
csstree.walk(arvore, {
visit: 'Rule',
enter(no) {
regras++;
console.log(`regra ${regras} · seletor: ${csstree.generate(no.prelude)}`);
no.block.children.forEach((decl) => {
declaracoes++;
console.log(` propriedade: ${decl.property.padEnd(17)}valor: ${csstree.generate(decl.value)}`);
});
},
});
console.log(`\ntotal: ${regras} regras e ${declaracoes} declarações`);total: 3 regras e 6 declarações
Três regras, seis declarações. O arquivo que você escreve como texto vira, para a máquina, uma lista de pares — e é só isso que ela guarda.
Como o navegador casa a regra com o elemento
O seletor é um endereço. Quando o navegador termina de ler a folha, ele pega
cada seletor e procura, dentro da página, quem responde por aquele endereço. É
a mesma busca que document.querySelectorAll faz — dá para reproduzir o
casamento regra por regra:
for (const regra of document.styleSheets[0].cssRules) {
const casaram = [...document.querySelectorAll(regra.selectorText)];
const tags = casaram.map((el) => `<${el.tagName.toLowerCase()}>`).join(', ');
console.log(`${regra.selectorText.padEnd(8)} casou com ${casaram.length} elemento: ${tags}`);
}Repare na última linha. O seletor .aviso não fala nada sobre parágrafo, mas
foi um <p> que apareceu — porque o único elemento com class="aviso" na
página é aquele parágrafo. O seletor procura uma condição, não uma tag. Se
amanhã o aviso virar uma <div class="aviso">, a regra continua valendo sem
uma linha de mudança.
Esse é o motivo de a classe ser a ferramenta preferida no dia a dia: ela deixa você marcar “isto é um aviso” sem prender o estilo à etiqueta escolhida. As outras formas de escrever endereço — descendente, filho, atributo, agrupamento — estão em seletores e cascata no CSS.
A folha de estilo que o navegador já traz de fábrica
Volte à pista da primeira seção. Abrindo a mesma página sem nenhum link de
CSS, e medindo propriedades que a nossa folha nem toca:
const ler = (sel, props) => {
const c = getComputedStyle(document.querySelector(sel));
console.log(`${sel.padEnd(7)} ${props.map((p) => `${p}: ${c[p]}`).join(' · ')}`);
};
ler('h1', ['display', 'font-size', 'font-weight', 'margin-top']);
ler('p', ['display', 'font-size', 'margin-top']);
ler('ul', ['display', 'padding-left', 'list-style-type']);
ler('li', ['display']);Nenhum desses valores veio de nós. Título em negrito, bolinha na lista, recuo de 40 pixels, espaço entre parágrafos: tudo isso é uma folha de estilo que o navegador aplica antes da sua, chamada user agent stylesheet. Ela existe para que uma página sem CSS ainda seja legível.
E ela não é segredo: no Chrome, é um arquivo html.css dentro do motor Blink.
Estes são os trechos que produziram os números acima, copiados do código-fonte:
h1 {
display: block;
font-size: 2em;
margin-block-start: 0.67__qem;
margin-block-end: 0.67em;
margin-inline-start: 0;
margin-inline-end: 0;
font-weight: bold
}
ul, menu, dir {
display: block;
list-style-type: disc;
margin-block-start: 1__qem;
margin-block-end: 1em;
padding-inline-start: 40px
}Agora as contas fecham. font-size: 2em sobre os 16px padrão dá exatamente os
32px que o h1 mostrou — e explica por que a nossa regra font-size: 32px não
mudou nada. A margem de 0.67em sobre 32px dá 21,44px, o valor que apareceu
com duas casas. (O __qem é uma unidade interna do Blink: um em marcado para
o quirks mode, o modo de compatibilidade com páginas antigas. Num arquivo seu,
isso seria só em.)
Dá para medir o tamanho dessa herança. O arquivo é público, então o primeiro
passo é baixar ele para o lado do anatomia.mjs:
curl -s "https://chromium.googlesource.com/chromium/src/+/refs/heads/main/third_party/blink/renderer/core/html/resources/html.css?format=TEXT" | base64 -d > ua.cssAgora é o mesmo css-tree da seção anterior, também no terminal, apontado para
o ua.css em vez do nosso arquivo:
import * as csstree from 'css-tree';
import fs from 'node:fs';
const folha = fs.readFileSync('ua.css', 'utf8').replaceAll('__qem', 'em');
let regras = 0;
let declaracoes = 0;
csstree.walk(csstree.parse(folha), {
visit: 'Rule',
enter(no) {
regras++;
no.block.children.forEach(() => declaracoes++);
},
});
console.log(`html.css do Blink: ${folha.split('\n').length} linhas`);
console.log(`${regras} regras e ${declaracoes} declarações antes de você escrever a primeira`);Novecentas e quarenta e cinco declarações já estão escritas antes de você abrir o editor. Nem todas valem na sua página — entram em jogo só as regras cujos seletores casam com as tags que você realmente usou. Mas o ponto se mantém: escrever CSS quase nunca é começar do zero, é discordar de alguma dessas.
Quatro regras para o mesmo elemento: quem ganha
Como várias folhas se somam, é normal duas regras pedirem coisas diferentes para o mesmo elemento. A palavra cascata, que dá o C do nome CSS, é justamente o critério de desempate.
A clínica quer destacar o item de urgência. Esta página tem um li só, com id e
classe, e uma folha cascata.css:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="cascata.css">
</head>
<body>
<ul>
<li id="urgencia" class="destaque">Atendimento de urgência — 24h</li>
</ul>
</body>
</html>E quatro regras acabaram mirando esse mesmo li:
li {
color: black;
}
li {
color: gray;
}
.destaque {
color: orange;
}
#urgencia {
color: crimson;
}Em vez de discutir, dá para apagar uma regra por vez e ver a cor mudar ao vivo.
deleteRule remove a regra pelo índice, e getComputedStyle diz quem assumiu o
lugar:
const item = document.querySelector('li');
const folha = document.styleSheets[0];
const cor = () => getComputedStyle(item).color;
console.log(`as 4 regras no ar ............. ${cor()}`);
folha.deleteRule(3);
console.log(`sem #urgencia ................. ${cor()}`);
folha.deleteRule(2);
console.log(`sem #urgencia e sem .destaque . ${cor()}`);
folha.deleteRule(1);
console.log(`só a primeira regra li ........ ${cor()}`);A ordem de vitória foi #urgencia, depois .destaque, depois o segundo li,
depois o primeiro. Dois critérios diferentes decidiram isso.
Entre #urgencia, .destaque e li, venceu quem é mais específico: um id
vale mais que uma classe, e uma classe vale mais que um nome de tag. Já entre os
dois li, que têm a mesma força, venceu quem veio por último no arquivo — o
gray sobrescreveu o black. É por isso que colar uma regra no fim da folha
costuma “resolver” o problema, e é também por isso que a folha vira uma bagunça
quando você faz só isso. A conta completa está em
especificidade no CSS.
A declaração que o navegador joga fora sem avisar
Este é o tropeço número um de quem começa, e ele não parece um erro. Veja a
regra abaixo: tem uma propriedade escrita em inglês britânico e um font-size
sem ponto e vírgula e sem unidade.
h1 {
colour: #1f6f4a;
font-size: 32
text-align: center;
}Salve isso como quebrado.css, ligue numa página que tenha só o <h1> da
clínica e abra. O navegador não reclama de nada: nenhuma linha vermelha no
console, nenhum aviso. Perguntando o que ele guardou:
const regra = document.styleSheets[0].cssRules[0];
const c = getComputedStyle(document.querySelector('h1'));
console.log(`declarações que sobreviveram: ${regra.style.length}`);
console.log(`regra como o navegador guardou: ${regra.cssText}`);
console.log(`color no h1: ${c.color}`);
console.log(`font-size no h1: ${c.fontSize}`);
console.log(`text-align no h1: ${c.textAlign}`);Zero. Das três declarações escritas, nenhuma sobrou, e a regra virou um par
de chaves vazio. colour foi descartada porque essa propriedade não existe. O
32 sem unidade também é inválido — e, como faltou o ponto e vírgula, o
navegador continuou lendo o valor até achar um, engolindo o text-align: center
junto.
O font-size: 32px que aparece na saída é a armadilha completa: ele veio da
folha de fábrica, não da sua regra. A página parece meio certa, e é por isso que
o erro sobrevive tanto tempo.
Como o navegador não avisa, use um analisador que avisa. De volta ao terminal, o
css-tree lê o mesmo quebrado.css, compara cada declaração com a gramática
oficial da propriedade e aponta a linha:
import * as csstree from 'css-tree';
import fs from 'node:fs';
const arvore = csstree.parse(fs.readFileSync('quebrado.css', 'utf8'), { positions: true });
csstree.walk(arvore, {
visit: 'Declaration',
enter(no) {
const { error } = csstree.lexer.matchDeclaration(no);
console.log(`linha ${no.loc.start.line}: ${no.property} — ${error ? error.message : 'ok'}`);
},
});A segunda mensagem mostra o estrago inteiro: no campo value do font-size
está escrito 32 e, embaixo, text-align: center. A seta aponta onde a
gramática se perdeu. Na prática do dia a dia você não roda isso na mão — o
DevTools risca a declaração inválida no painel Styles, e uma extensão de lint no
editor faz o mesmo enquanto você digita.
CSSOM: o estilo também vira estrutura de dados
Você já viu document.styleSheets sendo usado o artigo inteiro sem explicação.
Ele é o CSSOM, o modelo de objetos do CSS: assim como o HTML vira uma árvore
de elementos, a folha de estilo vira uma lista de objetos consultáveis e
editáveis.
const folha = document.styleSheets[0];
const regra = folha.cssRules[1];
console.log(`folhas carregadas: ${document.styleSheets.length}`);
console.log(`regras nesta folha: ${folha.cssRules.length}`);
console.log(`tipo do objeto: ${regra.constructor.name}`);
console.log(`seletor: ${regra.selectorText}`);
console.log(`declarações: ${regra.style.length} (${[...regra.style].join(', ')})`);
regra.style.color = 'crimson';
console.log(`h1 depois da mudança: ${getComputedStyle(document.querySelector('h1')).color}`);
folha.insertRule('li { color: #1f6f4a; }', folha.cssRules.length);
console.log(`regras agora: ${folha.cssRules.length} · cor do li: ${getComputedStyle(document.querySelector('li')).color}`);Cada regra é um objeto CSSStyleRule com selectorText e style. Mudar
style.color repintou o título na hora, e insertRule acrescentou uma quarta
regra com a página já aberta.
Isso responde uma dúvida comum de quem está começando: o CSS não é uma imagem congelada. Ele fica vivo na memória, ao lado da árvore DOM que o navegador montou, e é dessa dupla que sai o desenho final da tela.
O que você consegue mudar hoje, com cinco propriedades
Cinco propriedades já tiram uma página do estado cru. Estas são as que valem a pena decorar primeiro:
| propriedade | o que muda | exemplo |
|---|---|---|
color |
a cor do texto | color: #1f6f4a |
background-color |
a cor do fundo da caixa | background-color: #fff3cd |
font-size |
o tamanho da letra | font-size: 32px |
padding |
o espaço entre o texto e a borda | padding: 12px |
border-radius |
o arredondamento dos cantos | border-radius: 8px |
A folha final da clínica usa todas elas:
body {
background-color: #f4f1ea;
color: #2b2b2b;
font-family: system-ui, sans-serif;
}
h1 {
color: #1f6f4a;
font-size: 32px;
}
.aviso {
background-color: #fff3cd;
padding: 12px;
border-radius: 8px;
}Das cinco, padding é a que mais confunde no começo, porque ela não muda cor
nenhuma: ela muda tamanho. Medindo a altura real que o aviso ocupa na tela,
com e sem a folha:
const aviso = document.querySelector('.aviso');
const medir = () => {
const c = getComputedStyle(aviso);
console.log(`fundo: ${c.backgroundColor} · padding: ${c.padding} · cantos: ${c.borderRadius}`);
console.log(`altura ocupada na tela: ${aviso.getBoundingClientRect().height}px`);
};
console.log('--- com final.css ---');
medir();
document.styleSheets[0].disabled = true;
console.log('--- com a folha desligada ---');
medir();A linha de texto tem 18 pixels de altura. Com 12 de folga em cima e 12 embaixo, a caixa passou a ocupar 42. Uma declaração de uma linha mexeu na geometria da página, e essa conta entre conteúdo, espaço interno e borda é o assunto do box model.
O que vem depois
O próximo passo é o mais prático de todos: descobrir onde esse CSS mora. São
três formas de ligar a folha à página — arquivo externo, bloco <style> e
atributo style — e elas não são equivalentes, como
colocar CSS no HTML mostra. Depois disso vêm os
seletores de verdade, e aí a linguagem começa a render.
A ordem inteira de estudo, do primeiro seletor ao layout responsivo, está na trilha de CSS; o mapa do assunto, com o que estudar em cada etapa, fica no guia de CSS.
Prefere aprender em vídeo?
Tem aula sobre este assunto no nosso canal.
DevClub no YouTubeCURSO DE CSS DO ZERO - APRENDA NA PRÁTICA - TUTORIAL COMPLETOAssistir a aula
DevClub no YouTubeCSS DO ZERO - APRENDA NA PRÁTICA - TUTORIAL COMPLETOAssistir a aula
Perguntas frequentes
CSS é uma linguagem de programação?
--cor) e até condições, como @media e @supports, mas não tem laço, não tem função que você chama e nada roda passo a passo como em JavaScript.Preciso saber HTML antes de aprender CSS?
Por que a minha regra funciona no computador e some no celular?
CSS trava a página enquanto carrega?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Chromium 151.0.7922.34 headless e Node 24.16.0 com css-tree 3.2.1, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MDN — What is CSS? — developer.mozilla.org
- W3C — CSS Cascading and Inheritance Level 5 — w3.org
- Chromium — html.css, a folha de estilo do user agent do Blink — chromium.googlesource.com


