Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA

Guia completo

CSS: o que é, como funciona e por onde começar

O que o CSS faz numa página, em que ordem estudar cada assunto — seletor, box model, Flexbox, Grid — e onde quase todo iniciante trava no caminho.

22 lições em trilha27 artigos no guiaAtualizado em maio de 2026

CSS é a linguagem que diz ao navegador como cada elemento de uma página deve aparecer e onde deve ficar. O HTML entrega uma árvore de elementos com significado; o CSS entrega tamanho, cor, espaçamento e posição. Sem ele a página funciona — só que com a aparência que o navegador escolheu sozinho.

Este guia é o mapa: o que estudar, em que ordem, e onde as pessoas param. Todos os números que aparecem aqui foram medidos num Chromium 151 de verdade, controlado por Playwright, sobre a mesma página: o acervo de uma livraria de bairro, com três livros.

html
<h1 class="marca">Livraria da Esquina</h1>
<ul class="vitrine">
  <li class="livro"><h2>Grande Sertão</h2><p class="autor">Guimarães Rosa</p><p class="preco">R$ 74,90</p></li>
  <li class="livro"><h2>Vidas Secas</h2><p class="autor">Graciliano Ramos</p><p class="preco">R$ 39,90</p></li>
  <li class="livro"><h2>A Hora da Estrela</h2><p class="autor">Clarice Lispector</p><p class="preco">R$ 44,90</p></li>
</ul>

Esse arquivo não muda mais. Do começo ao fim do guia, o que muda é só a folha de estilo em cima dele — que é exatamente o ponto do CSS.

O que o CSS decide, e o que ele já encontra decidido

Existe uma frase repetida em toda aula de introdução: “o CSS dá aparência ao HTML”. Ela é falsa, e a diferença importa. Abra a página acima sem nenhuma folha de estilo e meça o que está na tela:

js
import { readFileSync } from 'node:fs';
import { chromium } from 'playwright-core';

const acervo = readFileSync('acervo.html', 'utf8');
const navegador = await chromium.launch();
const pagina = await navegador.newPage({ viewport: { width: 900, height: 700 } });
await pagina.setContent(`<!doctype html><body style="margin:0">${acervo}</body>`);

console.log(await pagina.$eval('.marca', (el) => {
  const s = getComputedStyle(el);
  return `h1.marca -> font-size ${s.fontSize} | font-weight ${s.fontWeight} | display ${s.display} | margin-top ${s.marginTop}`;
}));
console.log(await pagina.$$eval('.livro', (els) => els.map((el) => {
  const r = el.getBoundingClientRect();
  return `li.livro  -> x ${r.x} | y ${r.y} | largura ${r.width}`;
}).join('\n')));

await navegador.close();
h1.marca -> font-size 32px | font-weight 700 | display block | margin-top 21.44px li.livro -> x 40 | y 79.875 | largura 860 li.livro -> x 40 | y 199.6875 | largura 860 li.livro -> x 40 | y 319.5 | largura 860

Ninguém escreveu uma linha de CSS, e mesmo assim o h1 está com 32px, em negrito, ocupando a linha inteira, com 21,44px de margem no topo. Os li estão recuados 40px da esquerda e empilhados um sob o outro.

Isso vem da folha de estilo do agente de usuário — um CSS que o próprio navegador aplica antes de qualquer coisa sua. Então a sua folha de estilo não começa do zero: ela começa discordando de alguém. É por isso que quase todo projeto abre com um punhado de regras zerando margem e trocando box-sizing.

E há três coisas que o CSS não decide, e confundi-las é a origem de muita hora perdida:

  • O significado. Um div com font-size: 32px continua não sendo um título para o leitor de tela nem para o Google. Isso é trabalho do HTML — e o guia de HTML trata exatamente disso.
  • O comportamento. Abrir um menu ao clicar, buscar dados, validar campo: JavaScript. O CSS reage a estado (:hover, :checked, :focus), não a eventos.
  • A ordem dos elementos na árvore. O CSS reordena visualmente, com order e com Grid, mas a ordem de leitura e de foco continua sendo a do HTML. Layout que discorda demais do documento quebra a navegação por teclado.

A consequência prática: quando um layout se recusa a funcionar, olhe o HTML antes de acrescentar mais uma linha de CSS. Um elemento aninhado no lugar errado não se conserta com !important.

De folha de estilo a pixel: as quatro etapas

O navegador não “aplica CSS” num passo só. Ele faz quatro coisas em sequência, e saber em qual delas você está resolve a maior parte dos problemas.

1 2 3 4 cascata qual regra vence valor computado 1.125rem vira 18px layout a caixa ganha 284px pintura o pixel na tela

Coloque uma folha de estilo mínima na livraria e observe as três primeiras etapas acontecendo em cima da mesma declaração:

css
:root {
  --tinta: #22303c;
  --papel: #fdfaf3;
  --destaque: #b4471f;
  --respiro: 1.5rem;
}

html { font-size: 16px; }

body {
  margin: 0;
  width: 900px;
  background: var(--papel);
  color: var(--tinta);
  font-family: Georgia, serif;
}

.vitrine {
  list-style: none;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--respiro);
}

.livro {
  padding: var(--respiro);
  border: 1px solid #e3dccd;
}

.preco {
  font-size: 1.125rem;
  font-weight: 700;
  color: var(--destaque);
}
js
import { readFileSync } from 'node:fs';
import { chromium } from 'playwright-core';

const acervo = readFileSync('acervo.html', 'utf8');
const folha = readFileSync('livraria.css', 'utf8'); // a folha de estilo acima

const navegador = await chromium.launch();
const pagina = await navegador.newPage({ viewport: { width: 900, height: 700 } });
await pagina.setContent(`<!doctype html><style>${folha}</style>${acervo}`);

console.log(await pagina.$eval('.preco', (el) => {
  const s = getComputedStyle(el);
  return [
    `escrevi  font-size: 1.125rem   ->  o navegador computou  ${s.fontSize}`,
    `escrevi  color: var(--destaque) ->  o navegador computou  ${s.color}`,
    `nao escrevi font-family nenhuma ->  o navegador herdou    ${s.fontFamily}`,
  ].join('\n');
}));
console.log(await pagina.$eval('.livro', (el) => {
  const s = getComputedStyle(el);
  return `escrevi  padding: var(--respiro) ->  computou ${s.padding}, e so no layout virou uma caixa de ${el.getBoundingClientRect().width}px`;
}));

await navegador.close();
escrevi font-size: 1.125rem -> o navegador computou 18px escrevi color: var(--destaque) -> o navegador computou rgb(180, 71, 31) nao escrevi font-family nenhuma -> o navegador herdou Georgia, serif escrevi padding: var(--respiro) -> computou 24px, e so no layout virou uma caixa de 284px

Quatro fatos numa saída só, e cada um é um assunto inteiro do CSS:

  1. 1.125rem virou 18px. rem é uma unidade relativa à raiz do documento; como html está com font-size: 16px, a conta é 1,125 × 16.
  2. var(--destaque) virou rgb(180, 71, 31). Variáveis são resolvidas antes do layout, na etapa do valor computado.
  3. .preco ficou com Georgia sem ninguém pedir. É herança: algumas propriedades descem do pai para o filho sozinhas, e font-family é uma delas.
  4. Nenhuma dessas etapas sabia que a caixa teria 284px. Isso só existe depois que o layout roda, e depende da largura do pai, do gap e do número de colunas.

Guarde essa separação. Ela explica por que a aba “Computed” do DevTools mostra valores diferentes dos que você escreveu, e por que width: 100% não é um número até o navegador terminar de calcular a linha.

A ordem de estudo que funciona, assunto por assunto

Esta é a sequência da trilha de CSS. Não é a ordem da documentação: é a ordem em que os assuntos param de doer, porque cada um usa o anterior.

1. Como o CSS chega até a página

Antes de qualquer propriedade, entenda o que é uma regra e como ela é carregada: <link rel="stylesheet">, <style> no head, atributo style no elemento. O que é CSS cobre isso, e resolve de saída o problema número um de aluno iniciante — a folha que simplesmente não está sendo lida.

2. Seletores e cascata

Como escolher os elementos e o que acontece quando duas regras querem a mesma propriedade. É o assunto mais mal estudado do CSS, e o que mais devolve tempo depois. Está em seletores e cascata.

Onde as pessoas travam: em achar que a última regra escrita sempre vence. Não vence — a especificidade vem antes da ordem, e você vai ver isso medido daqui a pouco.

3. Box model e box-sizing

Todo elemento é uma caixa com conteúdo, padding, border e margin. Box model no CSS mostra como esses quatro anéis se somam e por que box-sizing: border-box deveria ser a primeira linha do seu projeto.

4. Unidades e cores

px, %, rem, em, vw, ch. Cada uma responde a uma pergunta diferente: “relativo a quê?”. Junto vêm as notações de cor — hex, rgb, hsl, oklch — e as custom properties, que transformam quinze valores repetidos em um só nome.

5. display e o fluxo normal

Antes de Flexbox e de Grid, entenda o padrão: block empilha, inline acompanha o texto. Metade dos problemas de layout é um elemento inline recebendo width, que ele ignora.

6. Flexbox

Uma dimensão: uma fileira ou uma coluna. justify-content, align-items, gap, flex-grow. É o que resolve barra de topo, cartão, botão com ícone. Flexbox no CSS tem o eixo principal e o eixo cruzado explicados com desenho — e entender esses dois eixos é 80% do assunto.

7. Grid

Duas dimensões: linhas e colunas ao mesmo tempo. CSS Grid trata de grid-template-columns, da unidade fr e de minmax(). É a ferramenta de página inteira e de galeria.

8. Responsividade

Media queries, container queries, clamp() e a mentalidade mobile-first. O objetivo não é ter três layouts; é ter um layout que suporta ser espremido.

9. position, z-index e overflow

relative, absolute, fixed, sticky. Aqui aparece o empilhamento — e a trava que vamos medir na quinta posição da próxima seção.

10. Transição, transformação e animação

transition, transform, @keyframes. É o último assunto de propósito: é o mais divertido e o menos essencial. Layout quebrado com animação bonita continua quebrado.

11. Um layout inteiro, terminado

Escolha uma página real — a da livraria serve — e termine. Cabeçalho, vitrine, rodapé, responsivo, estados de foco. Um layout concluído ensina mais que trinta exercícios avulsos, porque só nele aparecem os conflitos entre as partes.

Fluxo normal, Flexbox ou Grid: a tabela de decisão

Esta é a pergunta que mais aparece, e ela tem resposta objetiva. Pegue a mesma vitrine de três livros e troque só o display do container:

css
/* nada: o fluxo normal do navegador */

.vitrine { display: flex; gap: 16px; }

.vitrine { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
js
import { readFileSync } from 'node:fs';
import { chromium } from 'playwright-core';

const acervo = readFileSync('acervo.html', 'utf8');

const base = `*{box-sizing:border-box;margin:0}
  body{margin:0;width:900px;font:16px/1.4 system-ui}
  .vitrine{list-style:none;padding:0}
  .livro{padding:16px;border:1px solid #ccc}`;

const modos = {
  'fluxo normal': '',
  'Flexbox': '.vitrine{display:flex;gap:16px}',
  'Grid': '.vitrine{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}',
};

const navegador = await chromium.launch();
const pagina = await navegador.newPage({ viewport: { width: 900, height: 700 } });

for (const [nome, extra] of Object.entries(modos)) {
  await pagina.setContent(`<!doctype html><style>${base}${extra}</style>${acervo}`);
  const caixas = await pagina.$$eval('.livro', (els) => els.map((el) => {
    const r = el.getBoundingClientRect();
    return `x ${String(Math.round(r.x * 100) / 100).padStart(7)} | largura ${Math.round(r.width * 100) / 100}`;
  }));
  const altura = await pagina.$eval('.vitrine', (el) => el.getBoundingClientRect().height);
  console.log(`${nome}:`);
  caixas.forEach((c, i) => console.log(`  livro ${i + 1}: ${c}`));
  console.log(`  a vitrine inteira: ${altura}px de altura\n`);
}

await navegador.close();
fluxo normal: livro 1: x 0 | largura 900 livro 2: x 0 | largura 900 livro 3: x 0 | largura 900 a vitrine inteira: 337.125px de altura

Flexbox: livro 1: x 0 | largura 196.03 livro 2: x 212.03 | largura 170.5 livro 3: x 398.53 | largura 225.47 a vitrine inteira: 112.375px de altura

Grid: livro 1: x 0 | largura 289.33 livro 2: x 305.33 | largura 289.33 livro 3: x 610.66 | largura 289.34 a vitrine inteira: 112.375px de altura

Repare no que os números dizem, porque nenhum artigo consegue dizer isso melhor que a medida.

No fluxo normal, cada livro ocupa a largura inteira e a lista fica com 337px de altura: três caixas empilhadas.

No Flexbox, os três ficaram lado a lado com larguras diferentes: 196,03, 170,50 e 225,47. Ninguém pediu isso — é a regra. No Flexbox quem decide a largura é o conteúdo. “Grande Sertão / Guimarães Rosa” é um texto mais largo que “Vidas Secas / Graciliano Ramos”, e a caixa acompanha.

No Grid, os três ficaram com 289,33px cravados. repeat(3, 1fr) divide o espaço disponível em três partes iguais e o conteúdo se acomoda dentro. No Grid quem decide a largura é a grade.

Essa é a regra de decisão inteira, e ela cabe numa pergunta: quem decide o tamanho, o conteúdo ou a grade?

a situação fluxo normal Flexbox Grid
texto corrido, um bloco embaixo do outro sim não não
barra de topo: logo à esquerda, menu à direita não sim não
botão com ícone e rótulo alinhados não sim não
lista de etiquetas que quebra linha sozinha não sim não
vitrine de produtos com colunas iguais não não sim
página inteira: cabeçalho, conteúdo, barra lateral, rodapé não não sim
formulário com rótulo e campo alinhados em coluna não não sim
galeria que muda de 1 para 3 colunas sem media query não não sim
dois elementos sobrepostos na mesma célula não não sim
centralizar uma coisa só, na vertical e na horizontal não sim sim

Três critérios de desempate, quando a tabela não fechar:

Se as caixas têm tamanhos naturalmente diferentes e isso é desejável, é Flexbox. Menu, etiquetas, botões: o item mais longo pode ser mais largo.

Se você quer alinhamento entre linhas, é Grid. Flexbox com flex-wrap coloca os itens em várias linhas, mas cada linha se organiza sozinha — as colunas não se alinham entre si. É a diferença mais cara de descobrir tarde.

Se você está escrevendo width: 33.33% na mão, é Grid. Porcentagem para dividir espaço é como se fazia antes de 2017. 1fr faz a conta pelo navegador e já desconta o gap.

E os dois convivem. Contando as declarações de display da folha de estilo deste blog, saem 39 flex, 32 grid, 23 block, 21 none e 9 inline-flex — mais três valores avulsos, de uma ocorrência cada. Nenhum dos dois venceu: o Grid monta a página e o Flexbox monta os componentes dentro dela. Quem quiser ver os dois lado a lado num caso concreto, o artigo Flexbox ou Grid faz essa comparação inteira.

As cinco travas que aparecem em todo aluno

Não são cinco assuntos difíceis. São cinco comportamentos que contrariam a intuição, e todo mundo bate neles na mesma ordem.

1. A caixa de 200px que tem 236px

css
.a { width: 200px; padding: 16px; border: 2px solid #000; }
.b { width: 200px; padding: 16px; border: 2px solid #000; box-sizing: border-box; }
largura real de uma caixa declarada com width:200px sem box-sizing .. 236 com border-box .. 200

Por padrão, width mede só o conteúdo. padding e border são somados por fora: 200 + 16 + 16 + 2 + 2 = 236. Com box-sizing: border-box, os 200px passam a incluir tudo. É por isso que praticamente todo projeto sério começa com * { box-sizing: border-box; }.

2. A margem do filho que empurra o pai

css
.cartao { background: #eee; }
.cartao h2 { margin: 32px 0 0; }
h2 com margin-top:32px dentro do cartao y do topo do cartao ......... 32 altura do cartao ............ 28 altura com padding-top:1px .. 61

O h2 está dentro do cartão, mas a margem dele vazou: quem desceu 32px foi o cartão, não o título. E o cartão ficou com 28px de altura — só o texto. Isso é colapso de margem: a margem superior do primeiro filho se funde com a do pai quando não existe nada entre elas.

Um único pixel de padding-top no pai já corta o colapso, e a altura pula para 61. Na prática, resolva com padding no contêiner ou com display: flex — em container flex ou grid a margem não colapsa.

3. height: 100% que não vira nada

css
.painel { height: 100%; }
.painel-vh { height: 100dvh; }
height:100% com o pai sem altura declarada .painel (height:100%) .... 18 .painel-vh (height:100dvh) 700

100% significa “100% da altura do pai”. Se o pai não tem altura definida, não existe base para a conta, e o navegador cai no tamanho do conteúdo: 18px, a altura de uma linha de texto. Já 100dvh é 100% da altura da janela — 700px, o viewport do teste — e não depende de pai nenhum.

Regra prática: para “encher a tela”, use dvh ou min-height. Para “encher o pai”, garanta que o pai tenha altura, ou torne o pai um flex container.

4. A regra certa que perde a disputa

css
.vitrine .livro .preco { color: rgb(0,128,0); }   /* verde */
#destaque .preco       { color: rgb(180,71,31); } /* laranja */
.preco                 { color: rgb(0,0,255); }   /* azul, escrita depois */
p                      { color: rgb(255,0,0); }   /* vermelho, escrita por último */
quatro regras disputando a cor do preco cor final .. rgb(180, 71, 31)

A última regra escrita perdeu, e perdeu feio. Calculando a especificidade de cada seletor na notação (id, classe, elemento):

(0,0,1) p (0,1,0) .preco (0,3,0) .vitrine .livro .preco (1,1,0) #destaque .preco

Compara-se da esquerda para a direita, e a primeira coluna diferente decide. #destaque .preco tem um id, então ganha de qualquer quantidade de classes — mesmo de (0,3,0), que tem três. A ordem no arquivo só desempata regras com especificidade igual.

5. z-index: 999 que fica embaixo de z-index: 1

css
.capa  { position: relative; height: 60px; }          /* e, na segunda versão, opacity: .99 */
.selo  { position: absolute; z-index: 999; }
.barra { position: relative; z-index: 1; margin-top: -40px; }
sem opacity na capa — pilha no ponto (40,40): selo > barra > capa > body > html

com opacity:.99 na capa — pilha no ponto (40,40): barra > selo > capa > body > html

Na primeira versão o selo está por cima, como se espera. Na segunda, a única mudança foi um opacity: .99 no pai — e o selo com z-index: 999 foi parar embaixo da barra com z-index: 1.

O motivo é o contexto de empilhamento. Certas propriedades — opacity menor que 1, transform, filter, will-change, position: fixed — criam uma camada fechada. Dentro dela o z-index do filho vale; fora dela, o filho inteiro é empilhado como se fosse o pai. 999 só disputa com os irmãos dele.

O CSS de 2020 para cá, e o que vale usar hoje

Muito material de estudo ainda ensina soluções de contorno para problemas que a linguagem resolveu. Vale conferir o que o navegador de hoje já entende:

js
import { chromium } from 'playwright-core';

const navegador = await chromium.launch();
const pagina = await navegador.newPage();
await pagina.setContent('<!doctype html><p>');
console.log(`Chromium ${await navegador.version()} entende:`);

const recursos = [
  ['aspect-ratio', 'aspect-ratio: 3 / 4'],
  ['clamp()', 'font-size: clamp(1rem, 2vw, 1.5rem)'],
  ['oklch()', 'color: oklch(0.76 0.21 146)'],
  ['color-mix()', 'color: color-mix(in oklch, red 40%, blue)'],
  ['container query', 'container-type: inline-size'],
];
for (const [nome, decl] of recursos) {
  const [p, v] = decl.split(/:\s*/);
  const ok = await pagina.evaluate(([p, v]) => CSS.supports(p, v), [p, v]);
  console.log(' ', ok ? 'sim ' : 'nao ', nome);
}
for (const sel of [':has(.selo)', ':is(h1, h2)', '.livro:nth-child(2 of .livro)']) {
  const ok = await pagina.evaluate((s) => CSS.supports(`selector(${s})`), sel);
  console.log(' ', ok ? 'sim ' : 'nao ', `seletor ${sel}`);
}

await navegador.close();
Chromium 151.0.7922.34 entende: sim aspect-ratio sim clamp() sim oklch() sim color-mix() sim container query sim seletor :has(.selo) sim seletor :is(h1, h2) sim seletor .livro:nth-child(2 of .livro)

Quatro consequências práticas para quem está estudando agora:

Grade responsiva sem media query. Uma linha resolve o que antes exigia três pontos de quebra escritos à mão:

css
.vitrine {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}
viewport 360px -> 1 coluna(s), cada livro com 360px viewport 560px -> 2 coluna(s), cada livro com 272px viewport 1200px -> 3 coluna(s), cada livro com 389.33px

Uma declaração, três layouts, zero @media. O navegador cabe quantas colunas de no mínimo 240px couberem e distribui a sobra.

Aninhamento nativo. Você pode escrever regras dentro de regras sem pré-processador. E, se precisar suportar navegador antigo, uma ferramenta de build desmonta isso para você — aqui, o mesmo código passado pelo lightningcss com alvo Chrome 90, Safari 14 e Firefox 88:

css
.livro {
  color: oklch(0.42 0.09 250);
  padding: 16px;

  & .preco { color: oklch(0.58 0.16 40); font-weight: 700; }
  &:hover { border-color: currentColor; }
}
.livro { color: #214f7c; color: color(display-p3 .177772 .307594 .472426); color: lab(32.3393% -3.86982 -30.3918); padding: 16px; }

.livro .preco { color: #c55123; color: color(display-p3 .718866 .344953 .192117); color: lab(50% 45.885 49.3032); font-weight: 700; }

.livro:hover { border-color: currentColor; }

O aninhamento foi achatado e cada oklch() virou três declarações em cascata: o hex para quem não entende nada, e as versões de gama ampla para quem entende. Você escreve moderno; a ferramenta cuida do resto.

:has() deu ao CSS o seletor de pai. .livro:has(.esgotado) estiliza o cartão a partir de algo que está dentro dele. Antes, isso exigia JavaScript.

Container queries respondem ao pai, não à janela. É o que faltava para componente reutilizável: o mesmo cartão de livro pode se comportar de um jeito na vitrine larga e de outro na barra lateral estreita, sem saber o tamanho da tela.

O que você não precisa aprender agora

Metade da ansiedade de quem começa vem de uma lista de tecnologias que alguém disse ser obrigatória. Quase nada dessa lista é urgente.

Pré-processador (Sass, Less). Sass existiu porque o CSS não tinha variável, aninhamento nem função. Hoje tem os três nativos. Sass continua útil em projeto grande e em código legado, e você vai encontrar por aí — mas aprender antes do CSS puro é decorar a solução de um problema que você ainda não teve.

Metodologia de nomes (BEM, OOCSS, SMACSS). Convenções para nomear classe em time grande. Num projeto seu, com quatro arquivos, elas só acrescentam __elemento--modificador ao que já funcionava.

Framework de utilidades. Classe de framework é atalho para declaração de CSS. Sem saber o que align-items faz, items-center vira mágica — e mágica não se depura. Depois de dois layouts feitos na mão, um framework economiza tempo de verdade.

CSS-in-JS e componentes estilizados. São assunto de framework de interface, não de CSS. Vêm depois do React, não antes.

Decorar propriedades. O dicionário de propriedades do css-tree — o que um analisador de CSS precisa reconhecer — lista 704 nomes, sendo 575 sem prefixo de fabricante. A folha de estilo inteira deste blog usa 118 deles:

js
import { readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import * as csstree from 'css-tree';
import { transform } from 'lightningcss';

const dir = 'src/styles'; // rodando de dentro da pasta do blog
const arquivos = readdirSync(dir).filter((f) => f.endsWith('.css'));
const custom = new Set();
const propriedades = new Set();
let bytes = 0, minificados = 0, regras = 0, seletores = 0, declaracoes = 0, media = 0;

for (const nome of arquivos) {
  const css = readFileSync(join(dir, nome), 'utf8');
  bytes += Buffer.byteLength(css);
  minificados += transform({ filename: nome, code: Buffer.from(css), minify: true }).code.length;

  csstree.walk(csstree.parse(css), (node) => {
    if (node.type === 'Rule') regras++;
    if (node.type === 'Selector') seletores++;
    if (node.type === 'Atrule' && node.name === 'media') media++;
    if (node.type === 'Declaration') {
      declaracoes++;
      (node.property.startsWith('--') ? custom : propriedades).add(node.property);
    }
  });
}

console.log('arquivos .css .............', arquivos.length);
console.log('bytes escritos ............', bytes);
console.log('bytes minificados .........', minificados);
console.log('regras ....................', regras);
console.log('seletores .................', seletores);
console.log('declaracoes ...............', declaracoes);
console.log('custom properties definidas', custom.size);
console.log('@media ....................', media);
console.log('propriedades diferentes usadas:', propriedades.size);
arquivos .css ............. 8 bytes escritos ............ 73733 bytes minificados ......... 46406 regras .................... 452 seletores ................. 509 declaracoes ............... 1647 custom properties definidas 47 @media .................... 23 propriedades diferentes usadas: 118

Um site inteiro — blog, cabeçalho, busca, tipografia de artigo, blocos de código, tema claro e escuro — cabe em 452 regras e 118 propriedades diferentes. Vinte delas resolvem o dia a dia. O resto é consulta.

Quanto tempo leva, semana a semana

Com uma hora por dia, de forma consistente:

  • Semanas 1 e 2 — seletores, cascata, box model, unidades e cores. Você estiliza uma página estática e entende por que cada regra pegou.
  • Semanas 3 e 4display, fluxo normal, Flexbox inteiro. Você monta cabeçalho, cartão e barra de navegação sem consultar exemplo.
  • Semanas 5 a 7 — Grid, responsividade, media e container queries. Você monta a página inteira da livraria e ela sobrevive a ser espremida em 360px.
  • Semanas 8 a 10position, empilhamento, overflow, transições, estados de foco e contraste. Você conserta layout quebrado em vez de recomeçar.
  • Semanas 11 e 12 — um projeto terminado e publicado, com HTML semântico e acessibilidade de teclado.

Consistência vale mais que intensidade: uma hora por dia rende mais que sete horas no sábado, porque CSS se aprende vendo o efeito de cada mudança, e isso exige muitos ciclos curtos, não um bloco longo.

O teste dos três layouts

Existe um jeito honesto de saber se você já sabe CSS, e não é responder pergunta de múltipla escolha. É montar estes três layouts do zero, sem copiar, sem consultar exemplo pronto — só documentação:

Um. A barra de topo da livraria: o nome à esquerda, três links à direita, alinhados verticalmente no centro, com espaço igual entre os links, e que vire duas linhas em 360px.

Dois. A vitrine: cartões de livro que vão de uma coluna para três conforme a largura, sem nenhuma media query, com todas as capas na mesma proporção e os preços alinhados na base mesmo quando os títulos têm alturas diferentes.

Três. A ficha de um livro: capa à esquerda, texto à direita, um selo “esgotado” no canto superior da capa, e um botão fixo no rodapé da tela no celular que vira botão normal no desktop.

O teste não é conseguir. É conseguir explicar por que cada um usou o que usou: por que a barra é Flexbox, por que a vitrine é Grid, por que o selo precisa de um pai posicionado. Se você explica, o CSS deixou de ser tentativa e erro.

Por onde continuar

Comece pela primeira lição da trilha de CSS: as aulas estão na ordem desta página, cada uma com código executado e o erro mais comum reproduzido. Se ainda faltar base de HTML, faça o guia de HTML antes — CSS sem estrutura para estilizar é tempo perdido em dobro.

E mantenha a livraria aberta numa aba. Todo assunto novo deste guia cabe naquela página de três livros: é o mesmo arquivo do começo ao fim, e cada semana ele fica melhor.

Trilha

CSS

Layout, Flexbox, Grid e responsividade: da folha em branco à interface que se comporta em qualquer tela.

Ver a trilha
  1. 01O que é CSS e como o navegador aplica estilo na página
  2. 02Como colocar CSS no HTML: link, style e inline
  3. 03Pseudo-classes e pseudo-elementos: :hover e ::before
  4. 04:has() no CSS: o seletor que olha para o filho
  5. 05Unidades no CSS: px, rem, em, %, vw e ch
  6. 06Cores no CSS: hex, rgb, hsl e oklch na prática
  7. 07Variáveis CSS: custom properties na prática
  8. 08overflow no CSS: hidden, auto, scroll e clip
  9. 09background, border-radius e box-shadow no CSS
  10. 10display no CSS: block, inline, inline-block e none
  11. 11position no CSS: relative, absolute, fixed e sticky
  12. 12Como centralizar uma div no CSS: 6 formas testadas
  13. 13justify-content e align-items: alinhar no Flexbox
  14. 14flex-grow, flex-shrink e flex-basis: dividir o espaço
  15. 15CSS Grid: grid-template-columns, rows e a unidade fr
  16. 16grid-template-areas: desenhar o layout com nomes
  17. 17auto-fit e minmax(): grid responsivo sem media query
  18. 18Media queries no CSS: responsivo que não quebra
  19. 19Container queries: o componente responde ao pai
  20. 20Tipografia no CSS: fonte, escala e clamp() fluido
  21. 21transition e transform no CSS: o hover que desliza
  22. 22animation e @keyframes: animar CSS sem JavaScript

css

Perguntas frequentes

Preciso saber HTML antes de estudar CSS?
Precisa do suficiente para escrever uma página com títulos, parágrafos, listas e links, e para entender o que são classe e id. CSS só sabe trabalhar em cima de uma árvore de elementos que já existe.
Quanto tempo leva para aprender CSS?
Entre oito e doze semanas, com uma hora por dia, até montar um layout responsivo inteiro sem consultar exemplo pronto. As primeiras quatro semanas são as mais lentas, porque box model e cascata ainda são novidade.
Devo aprender Flexbox ou Grid primeiro?
Flexbox primeiro, porque quase todo componente pequeno — barra de topo, botão com ícone, cartão — é uma fileira ou uma coluna. Grid vem logo depois, quando você precisar de linhas e colunas ao mesmo tempo.
Ainda vale aprender CSS puro se existe Tailwind?
Vale, e é pré-requisito. Classe do Tailwind é atalho para declaração de CSS. Quem não sabe o que faz align-items não sabe o que faz items-center, e trava no primeiro layout que sai do padrão.
Por que a minha regra de CSS não está sendo aplicada?
Nas quatro causas mais comuns, nessa ordem: a folha não está sendo carregada, o seletor não casa com o elemento, outra regra mais específica venceu, ou a propriedade não faz efeito naquele tipo de caixa.
Preciso decorar todas as propriedades do CSS?
Não. O dicionário de propriedades do analisador css-tree lista 575 nomes sem prefixo de fabricante, e a folha de estilo inteira deste blog usa 118 deles. Vinte resolvem o dia a dia; o resto você consulta.

O código deste guia foi executado em Node 24.16.0 com Chromium 151.0.7922.34 via Playwright, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN Web Docs — CSS — developer.mozilla.org
  2. W3C — CSS Cascading and Inheritance Level 5 — w3.org
  3. W3C — CSS Snapshot 2023 — w3.org
  4. MDN Web Docs — CSS grid layout — developer.mozilla.org