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.
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.
<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:
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();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
divcomfont-size: 32pxcontinua 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
ordere 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.
Coloque uma folha de estilo mínima na livraria e observe as três primeiras etapas acontecendo em cima da mesma declaração:
: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);
}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();Quatro fatos numa saída só, e cada um é um assunto inteiro do CSS:
1.125remvirou18px.remé uma unidade relativa à raiz do documento; comohtmlestá comfont-size: 16px, a conta é 1,125 × 16.var(--destaque)virourgb(180, 71, 31). Variáveis são resolvidas antes do layout, na etapa do valor computado..precoficou com Georgia sem ninguém pedir. É herança: algumas propriedades descem do pai para o filho sozinhas, efont-familyé uma delas.- Nenhuma dessas etapas sabia que a caixa teria 284px. Isso só existe depois
que o layout roda, e depende da largura do pai, do
gape 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:
/* nada: o fluxo normal do navegador */
.vitrine { display: flex; gap: 16px; }
.vitrine { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }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();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
.a { width: 200px; padding: 16px; border: 2px solid #000; }
.b { width: 200px; padding: 16px; border: 2px solid #000; box-sizing: border-box; }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
.cartao { background: #eee; }
.cartao h2 { margin: 32px 0 0; }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
.painel { height: 100%; }
.painel-vh { height: 100dvh; }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
.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 */A última regra escrita perdeu, e perdeu feio. Calculando a especificidade de cada seletor na notação (id, classe, elemento):
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
.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; }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:
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();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:
.vitrine {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
}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:
.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 .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:
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);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 4 —
display, 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 10 —
position, 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.
- 01O que é CSS e como o navegador aplica estilo na página
- 02Como colocar CSS no HTML: link, style e inline
- 03Pseudo-classes e pseudo-elementos: :hover e ::before
- 04:has() no CSS: o seletor que olha para o filho
- 05Unidades no CSS: px, rem, em, %, vw e ch
- 06Cores no CSS: hex, rgb, hsl e oklch na prática
- 07Variáveis CSS: custom properties na prática
- 08overflow no CSS: hidden, auto, scroll e clip
- 09background, border-radius e box-shadow no CSS
- 10display no CSS: block, inline, inline-block e none
- 11position no CSS: relative, absolute, fixed e sticky
- 12Como centralizar uma div no CSS: 6 formas testadas
- 13justify-content e align-items: alinhar no Flexbox
- 14flex-grow, flex-shrink e flex-basis: dividir o espaço
- 15CSS Grid: grid-template-columns, rows e a unidade fr
- 16grid-template-areas: desenhar o layout com nomes
- 17auto-fit e minmax(): grid responsivo sem media query
- 18Media queries no CSS: responsivo que não quebra
- 19Container queries: o componente responde ao pai
- 20Tipografia no CSS: fonte, escala e clamp() fluido
- 21transition e transform no CSS: o hover que desliza
- 22animation e @keyframes: animar CSS sem JavaScript
css
Flexbox ou Grid: qual usar em cada tipo de layout
z-index não funciona: 5 causas e a correção de cada
Stacking context: como o CSS empilha as camadas
margin-top não funciona: a margem que colapsa no CSS
Herança no CSS: o que o filho recebe do pai sem pedir
Perguntas frequentes
Preciso saber HTML antes de estudar CSS?
Quanto tempo leva para aprender CSS?
Devo aprender Flexbox ou Grid primeiro?
Ainda vale aprender CSS puro se existe Tailwind?
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?
Preciso decorar todas as propriedades do CSS?
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
- MDN Web Docs — CSS — developer.mozilla.org
- W3C — CSS Cascading and Inheritance Level 5 — w3.org
- W3C — CSS Snapshot 2023 — w3.org
- MDN Web Docs — CSS grid layout — developer.mozilla.org




