// apostila completa · pt-BR

Tailwind CSS: do zero utilitário ao nível de mercado

Você já sabe CSS — esta apostila traduz esse conhecimento para o framework mais pedido em vagas de front-end. Teoria enxuta, exercícios com solução, três projetos guiados, perguntas de entrevista e um cheatsheet para o dia a dia.

utility-first responsivo dark-mode @theme cva + tailwind-merge shadcn/ui entrevistas
módulo 1 · capítulo 01

O que é Tailwind e por que o mercado usa

Tailwind CSS é um framework utility-first: em vez de escrever CSS em arquivos separados, você compõe o visual aplicando pequenas classes utilitárias diretamente no HTML. Cada classe faz uma coisa só — p-4 aplica padding, flex ativa flexbox, text-center centraliza texto.

O mesmo botão, duas abordagens

Como você já domina CSS, a melhor porta de entrada é uma comparação direta:

abordagem tradicional (BEM)
/* styles.css */
.btn-primary {
  background-color: #0ea5e9;
  color: white;
  padding: 0.5rem 1rem;
  border-radius: 0.5rem;
  font-weight: 600;
}
.btn-primary:hover { background-color: #0284c7; }

<!-- index.html -->
<button class="btn-primary">Salvar</button>
abordagem Tailwind
<button class="bg-sky-500 hover:bg-sky-600 text-white px-4 py-2 rounded-lg font-semibold">
  Salvar
</button>

Por que as empresas adotaram em massa

  • Velocidade: você não sai do HTML/JSX, não inventa nomes de classe (.card__header--active nunca mais) e não navega entre arquivos.
  • Consistência: a escala de design (espaçamentos, cores, tamanhos) é limitada e padronizada. Dois devs diferentes chegam a resultados visualmente coerentes.
  • Manutenção segura: apagar um componente apaga o estilo junto. Não existe "CSS morto" acumulando por medo de quebrar algo em outra página.
  • Bundle mínimo: o compilador só gera o CSS das classes que você realmente usou — projetos grandes ficam com ~10 kB de CSS.
  • Ecossistema: shadcn/ui, Radix, Headless UI, templates, plugins — o mercado inteiro gravita em torno dele.
Visão de mercadoTailwind aparece com enorme frequência em vagas de front-end júnior e pleno no Brasil, quase sempre ao lado de React/Next.js. Saber "traduzir" um layout do Figma para Tailwind com fluidez é uma habilidade avaliada em testes práticos de contratação.

Críticas comuns (e como respondê-las em entrevista)

  • "O HTML fica poluído" — verdade no começo; em projetos reais a repetição vive dentro de componentes (React/Vue), então cada trecho de classes existe em um único lugar.
  • "É inline style com outro nome" — não: utilitários respeitam uma escala de design, suportam estados (hover:), media queries (md:) e pseudo-elementos, coisas que style="" não faz.
  • "Você não aprende CSS" — Tailwind é um mapeamento 1:1 do CSS. Quem sabe CSS (seu caso) aprende Tailwind em dias; quem não sabe, continua precisando aprender os conceitos.

Exercícios — Capítulo 1

  1. Liste três problemas de manutenção de CSS que você já viveu e explique, em uma frase cada, como o utility-first ataca cada um.
  2. Sem consultar nada, tente "adivinhar" as classes Tailwind para: display:flex, margin-top:1rem, text-align:right. A previsibilidade dos nomes é proposital.
Ver gabarito
display:flex        → flex
margin-top:1rem     → mt-4   (escala: 4 = 1rem)
text-align:right    → text-right
módulo 1 · capítulo 02

Instalação e setup (v4 e v3)

Em 2026 o padrão é o Tailwind v4, que simplificou radicalmente o setup: zero arquivo de configuração obrigatório, detecção automática de conteúdo e configuração feita no próprio CSS. Muitas empresas ainda mantêm projetos em v3, então você verá os dois.

Opção A — Vite (o setup mais comum em vagas com React)

terminal
# 1. criar o projeto
npm create vite@latest meu-app -- --template react
cd meu-app

# 2. instalar tailwind + plugin do vite
npm install tailwindcss @tailwindcss/vite
vite.config.js
import tailwindcss from '@tailwindcss/vite'

export default {
  plugins: [tailwindcss()],
}
src/index.css
/* No v4, UMA linha substitui as três diretivas antigas */
@import "tailwindcss";

Pronto. Rode npm run dev e use classes em qualquer componente.

Opção B — Play CDN (só para estudar e prototipar)

index.html
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
Nunca em produçãoO CDN compila no navegador do usuário — é ótimo para praticar os exercícios desta apostila em um único arquivo HTML, mas lento e inadequado para um produto real. Em entrevistas, saber essa diferença conta ponto.

v3 vs v4: o que mudou (pergunta frequente de entrevista)

AspectoTailwind v3Tailwind v4
Configuraçãotailwind.config.js em JavaScriptDireto no CSS com @theme (config JS ainda é suportada)
Import no CSS@tailwind base; @tailwind components; @tailwind utilities;@import "tailwindcss";
Detecção de arquivosManual, via array content: [...]Automática (respeita .gitignore)
MotorPostCSS puroNovo engine em Rust (Oxide) — builds até 10× mais rápidos
CoresRGB/HSLOKLCH (cores mais vivas em telas modernas)
Recursos novosContainer queries nativas, variáveis CSS para todos os tokens, @starting-style

Ferramentas essenciais do dia a dia

  • Tailwind CSS IntelliSense (extensão do VS Code): autocomplete, preview de cores e lint. Instalação obrigatória — sério.
  • Prettier + prettier-plugin-tailwindcss: ordena as classes automaticamente na ordem oficial. Padrão em quase toda empresa que usa Tailwind.
  • Documentação oficial (tailwindcss.com): a busca (Ctrl+K) é sua melhor amiga; devs profissionais consultam o tempo todo.

Exercícios — Capítulo 2

  1. Crie um projeto Vite + React com Tailwind v4 seguindo os passos acima e confirme que <h1 class="text-3xl font-bold text-sky-500">Funcionou</h1> aparece grande, em negrito e azul.
  2. Crie um arquivo playground.html com o Play CDN — você vai usá-lo nos exercícios dos próximos capítulos.
  3. Instale a extensão IntelliSense e o plugin do Prettier e observe a ordenação automática das classes ao salvar.
módulo 1 · capítulo 03

A mentalidade utility-first

A maior barreira para quem vem do CSS tradicional não é técnica, é mental. A regra de ouro: pense em propriedades, não em componentes. Você não cria um "card" — você compõe fundo branco + borda arredondada + sombra + padding.

Anatomia de uma classe Tailwind

anatomia
  md:hover:bg-sky-500/75
  ─┬ ──┬── ─┬ ─┬─ ─┬─ ┬─
   │    │   │  │   │  └─ opacidade (75%)
   │    │   │  │   └──── tom da cor (100–950)
   │    │   │  └──────── cor da paleta
   │    │   └─────────── propriedade (background)
   │    └─────────────── estado (só no hover)
   └──────────────────── breakpoint (só em telas ≥ 768px)

Lendo da direita para a esquerda: "fundo sky-500 com 75% de opacidade, quando houver hover, a partir do breakpoint md". Toda a sintaxe do Tailwind é combinação desse padrão variante:variante:utilitário.

Dicionário CSS → Tailwind

Seu conhecimento de CSS mapeia quase 1:1. Memorize os padrões, não as classes:

CSS que você já sabeTailwindPadrão
padding: 1remp-4escala ×0.25rem
margin-inline: automx-autoeixos: x, y, t, r, b, l
display: flexflexdisplays têm nome direto
justify-content: space-betweenjustify-betweenprefixo da propriedade + valor
font-size: 1.25remtext-xlescala em "camisetas" (xs–9xl)
color: #ffftext-whitetext- para cor de texto
width: 100%w-fullfrações: w-1/2, w-2/3
border-radius: 9999pxrounded-fullnone, sm, md, lg, xl, 2xl, full

Fluxo de trabalho profissional

  1. Estruture o HTML semântico primeiro (header, main, section, button — semântica conta em acessibilidade e em code review).
  2. Layout de fora para dentro: container → grid/flex → espaçamentos → tipografia → cores → estados → responsivo.
  3. Extraia componentes quando repetir: a terceira vez que colar as mesmas classes, vire um componente React/Vue (não uma classe CSS — cap. 14).
Dica de quem sabe CSSQuando não souber a classe, escreva o CSS mentalmente e "traduza". Não lembra a classe de flex-wrap: wrap? É flex-wrap. De text-transform: uppercase? É uppercase. O naming do Tailwind quase sempre é a versão encurtada e óbvia.

Exercícios — Capítulo 3

  1. Traduza para Tailwind: display:grid; grid-template-columns:repeat(3,1fr); gap:1.5rem;
  2. Traduza para CSS puro: class="fixed inset-0 bg-black/50 backdrop-blur-sm" (um overlay de modal clássico).
  3. No seu playground, monte um card com: fundo branco, cantos rounded-2xl, sombra, padding interno e um título em negrito. Só com utilitários.
Ver gabarito
1) grid grid-cols-3 gap-6

2) position:fixed; inset:0;
   background-color: rgb(0 0 0 / 0.5);
   backdrop-filter: blur(4px);

3) <div class="bg-white rounded-2xl shadow-lg p-6">
     <h2 class="text-lg font-bold">Meu card</h2>
     <p class="text-slate-600">Conteúdo…</p>
   </div>
módulo 1 · capítulo 04

Espaçamento, cores e tipografia

A escala de espaçamento: a espinha dorsal do Tailwind

Quase tudo (padding, margin, gap, width, height) usa a mesma escala: 1 unidade = 0.25rem = 4px. Isso força consistência — o motivo nº 1 pelo qual designs em Tailwind "parecem profissionais".

ClasseValorPixelsUso típico
p-10.25rem4pxmicro-ajustes
p-20.5rem8pxpadding de badges/inputs compactos
p-41rem16pxpadding padrão de cards e botões
p-61.5rem24pxcards espaçosos
p-82rem32pxseções internas
p-16 / p-244rem / 6rem64 / 96pxrespiro entre seções de página

Direções e eixos

padrão de direções (vale para p- e m-)
p-4    → todos os lados
px-4   → eixo horizontal (left + right)
py-4   → eixo vertical (top + bottom)
pt-4 pr-4 pb-4 pl-4   → lados individuais
ps-4 pe-4             → início/fim lógicos (RTL-safe, ponto extra em entrevista)
-mt-4                 → margens negativas: prefixo "-"

gap e space-*

Em flex/grid, prefira gap-4 (o próprio gap do CSS). O utilitário space-y-4 aplica margem entre filhos diretos — útil fora de flex/grid, como empilhar parágrafos.

Cores: paleta e tons

Cada cor tem 11 tons, de 50 (quase branco) a 950 (quase preto). As famílias: slate gray zinc neutral stone red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose.

onde a cor se aplica muda o prefixo
text-sky-600      → cor do texto
bg-sky-100        → cor de fundo
border-sky-300    → cor da borda
ring-sky-500      → anel de foco
fill-sky-500      → preenchimento de SVG
bg-sky-500/60     → qualquer cor aceita /opacidade
Receita profissional de coresEscolha UMA cor de marca (ex.: sky) e UM cinza (ex.: slate) e construa a interface inteira com eles: texto principal text-slate-900, secundário text-slate-500, bordas border-slate-200, fundos bg-slate-50. Reserve a cor de marca para ações e destaques. Interfaces "poluídas" quase sempre misturam cinzas de famílias diferentes.

Tipografia

GrupoClasses
Tamanhotext-xs text-sm text-base text-lg text-xltext-9xl (cada uma já define um line-height adequado)
Pesofont-light font-normal font-medium font-semibold font-bold
Famíliafont-sans font-serif font-mono (customizável — cap. 13)
Altura de linhaleading-none leading-tight leading-relaxed ou text-lg/8 (tamanho/linha juntos)
Espaço entre letrastracking-tight tracking-wide
Outrositalic uppercase capitalize truncate line-clamp-3 text-balance
hierarquia tipográfica típica de produto
<h1 class="text-4xl font-bold tracking-tight text-slate-900">Título da página</h1>
<p  class="mt-2 text-lg text-slate-600">Subtítulo explicativo.</p>
<span class="text-xs font-medium uppercase tracking-wider text-slate-400">Rótulo</span>
Rótulo
Título da página
Subtítulo explicativo.

Exercícios — Capítulo 4

  1. Monte um "badge de status" (Ativo): fundo emerald-100, texto emerald-700, text-xs, font-medium, px-2 py-0.5, rounded-full.
  2. Crie a hierarquia de um artigo: título 3xl bold, data em xs uppercase cinza, parágrafos text-slate-600 leading-relaxed com space-y-4 no container.
  3. Refaça o exercício 1 trocando apenas as cores para uma versão de "erro" e uma de "aviso" — perceba como a escala de tons torna isso mecânico.
Ver gabarito (ex. 1)
<span class="inline-block rounded-full bg-emerald-100
             px-2 py-0.5 text-xs font-medium text-emerald-700">
  Ativo
</span>
módulo 1 · capítulo 05

Tamanhos, bordas, sombras e fundos

Width e height

sizing
w-64        → width: 16rem (escala de espaçamento)
w-full      → width: 100%
w-screen    → width: 100vw
w-1/2  w-2/3  w-3/4   → frações
max-w-md  max-w-3xl   → larguras máximas de leitura/containers
min-h-screen          → min-height: 100vh (base de layouts de página)
size-10               → width + height juntos (avatares, ícones)
h-dvh                 → 100dvh — correto em mobile (barra do navegador)
Padrão de container mais usado do mercado<div class="mx-auto max-w-6xl px-4"> — centraliza, limita a largura e garante respiro lateral no mobile. Você digitará isso milhares de vezes.

Bordas e anéis (rings)

borders & rings
border               → 1px sólida
border-2 border-dashed border-slate-300
rounded-lg rounded-full rounded-t-2xl   → raio (com variações por lado)
divide-y divide-slate-200               → borda ENTRE filhos (listas!)
ring-2 ring-sky-500 ring-offset-2       → anel via box-shadow, não afeta layout
outline-none                            → cuidado: sempre substitua por um focus visível

Sombras e efeitos

shadows & filters
shadow-sm shadow-md shadow-lg shadow-xl shadow-2xl
shadow-sky-500/30      → sombras coloridas (glow de botões)
opacity-50
blur-sm  grayscale  brightness-110       → filters
backdrop-blur-md bg-white/70             → efeito "vidro" (glassmorphism)

Fundos e gradientes

backgrounds
bg-gradient-to-r from-sky-500 to-indigo-500          → gradiente 2 cores
bg-gradient-to-br from-rose-500 via-purple-500 to-indigo-500
bg-cover bg-center bg-no-repeat                       → imagens de fundo
bg-[url('/hero.jpg')]                                 → URL arbitrária (cap. 15)
bg-gradient-to-r from-sky-500 to-indigo-500

Exercícios — Capítulo 5

  1. Crie um avatar: size-12 rounded-full ring-2 ring-white shadow-md em uma <img>. Depois empilhe três avatares sobrepostos usando -space-x-3 no container (dica: flex).
  2. Crie um botão "glow": fundo sky-500, shadow-lg shadow-sky-500/50, texto branco, rounded-xl.
  3. Monte um header "vidro": sticky top-0 backdrop-blur-md bg-white/70 border-b border-slate-200.
Ver gabarito (ex. 1)
<div class="flex -space-x-3">
  <img class="size-12 rounded-full ring-2 ring-white shadow-md" src="a.jpg" alt="Ana">
  <img class="size-12 rounded-full ring-2 ring-white shadow-md" src="b.jpg" alt="Bia">
  <img class="size-12 rounded-full ring-2 ring-white shadow-md" src="c.jpg" alt="Caio">
</div>
módulo 2 · capítulo 06

Flexbox

Você já sabe flexbox — aqui é só vocabulário. A tradução é literal:

CSSTailwind
display:flex / inline-flexflex / inline-flex
flex-direction: columnflex-col (e flex-row, flex-col-reverse…)
justify-content: center | space-betweenjustify-center | justify-between
align-items: center | startitems-center | items-start
flex-wrap: wrapflex-wrap
flex: 1 1 0% / flex-grow / flex-shrink:0flex-1 / grow / shrink-0
align-self: centerself-center
gap: 1remgap-4 (e gap-x-4, gap-y-2)

Os 4 padrões flex que resolvem 90% do trabalho

padrões de mercado
<!-- 1. Navbar: logo à esquerda, links à direita -->
<nav class="flex items-center justify-between px-6 py-4"></nav>

<!-- 2. Centralização perfeita (o clássico) -->
<div class="flex min-h-screen items-center justify-center"></div>

<!-- 3. Item de lista: avatar fixo + texto que cresce + ação -->
<li class="flex items-center gap-3">
  <img class="size-10 shrink-0 rounded-full" src="…" alt="">
  <div class="min-w-0 flex-1">
    <p class="truncate font-medium">Nome muito comprido…</p>
  </div>
  <button class="shrink-0"></button>
</li>

<!-- 4. Footer "grudado" no fim da página -->
<body class="flex min-h-dvh flex-col">
  <main class="flex-1"></main>
  <footer></footer>
</body>
O bug do truncateNo padrão 3, o min-w-0 no filho flex-1 é o que permite o truncate funcionar — sem ele, o conteúdo estoura o container. É um dos bugs de flexbox mais perguntados em pair programming; saber a causa (min-width:auto padrão de flex items) impressiona.

Exercícios — Capítulo 6

  1. Construa a navbar do padrão 1 com um logo textual, três links (flex gap-6) e um botão de CTA.
  2. Construa o item de lista do padrão 3 e teste com um nome de 60 caracteres — confirme o truncate.
  3. Crie uma "media card" horizontal: imagem w-32 fixa + coluna de conteúdo com título, descrição e um rodapé alinhado ao fim (flex-col + mt-auto).
módulo 2 · capítulo 07

Grid

CSSTailwind
display:gridgrid
grid-template-columns: repeat(3, 1fr)grid-cols-3
grid-column: span 2col-span-2
grid-row: span 2 / posiçãorow-span-2 / col-start-2
grid-template-columns: 240px 1frgrid-cols-[240px_1fr] (valor arbitrário)
place-items: centerplace-items-center

Os 3 layouts grid do dia a dia

grids de mercado
<!-- 1. Galeria de cards responsiva (o mais comum em teste técnico) -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3"></div>

<!-- 2. App shell: sidebar fixa + conteúdo fluido -->
<div class="grid min-h-dvh grid-cols-[260px_1fr]">
  <aside class="bg-slate-900"></aside>
  <main class="p-8"></main>
</div>

<!-- 3. Bento grid (tendência forte em landing pages) -->
<div class="grid grid-cols-4 grid-rows-2 gap-4">
  <div class="col-span-2 row-span-2 rounded-2xl bg-slate-100">Destaque</div>
  <div class="col-span-2 rounded-2xl bg-slate-100">B</div>
  <div class="rounded-2xl bg-slate-100">C</div>
  <div class="rounded-2xl bg-slate-100">D</div>
</div>
Destaque
B
C
D
Flex ou grid? (pergunta clássica)Resposta que agrada entrevistador: flex para distribuir itens em UM eixo (navbars, listas, alinhamentos); grid quando o layout é bidimensional ou precisa de trilhas previsíveis (galerias, dashboards, app shells). Em Tailwind os dois custam o mesmo — a escolha é conceitual, igual ao CSS puro.

Exercícios — Capítulo 7

  1. Monte a galeria responsiva (padrão 1) com 6 cards e confirme o comportamento redimensionando a janela.
  2. Reproduza o bento grid acima e depois inverta o destaque para o canto direito usando col-start-3.
  3. Construa o app shell do padrão 2 e coloque um header sticky top-0 dentro do main.
módulo 2 · capítulo 08

Position, z-index e overflow

posicionamento
static relative absolute fixed sticky      → position
top-0 right-4 bottom-2 left-1/2            → offsets (mesma escala)
inset-0                                    → top/right/bottom/left: 0
inset-x-0 inset-y-0                        → por eixo
z-10 z-50 -z-10                            → z-index
overflow-hidden overflow-y-auto overflow-x-scroll

Padrões que caem em teste técnico

receitas de posicionamento
<!-- Badge de notificação no canto do ícone -->
<button class="relative">
  🔔
  <span class="absolute -top-1 -right-1 size-4 rounded-full bg-rose-500
               text-[10px] font-bold text-white grid place-items-center">3</span>
</button>

<!-- Modal com overlay -->
<div class="fixed inset-0 z-50 grid place-items-center bg-black/50 p-4">
  <div class="w-full max-w-md rounded-2xl bg-white p-6 shadow-2xl"></div>
</div>

<!-- Centralizar absoluto (o truque translate) -->
<div class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"></div>

<!-- Imagem cobrindo card com conteúdo por cima -->
<div class="relative h-64 overflow-hidden rounded-2xl">
  <img class="absolute inset-0 size-full object-cover" src="…" alt="">
  <div class="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent"></div>
  <h3 class="absolute bottom-4 left-4 text-white font-bold">Título</h3>
</div>
object-cover é o novo background-sizeobject-cover + size-full em uma <img> substitui o antigo hábito de usar background-image para fotos — mantendo semântica, alt e lazy loading. É o padrão moderno.

Exercícios — Capítulo 8

  1. Crie o card com imagem + gradiente + título sobreposto (última receita acima).
  2. Monte um botão flutuante de "voltar ao topo": fixed bottom-6 right-6 z-40.
  3. Faça um cabeçalho de tabela sticky top-0 dentro de um container max-h-80 overflow-y-auto.
módulo 2 · capítulo 09

Responsividade mobile-first

Regra que você precisa internalizar: classes sem prefixo valem para TODAS as telas; prefixos aplicam a partir daquele breakpoint para cima. Tailwind é mobile-first, como as boas práticas de CSS moderno.

PrefixoMin-widthPense em
(nenhum)0celular
sm:640pxcelular grande / paisagem
md:768pxtablet
lg:1024pxnotebook
xl:1280pxdesktop
2xl:1536pxtelas grandes
o padrão mental correto
<!-- ERRADO (mentalidade desktop-first): -->
<div class="grid-cols-3 sm:grid-cols-1">  ✗ não faz o que parece

<!-- CERTO: comece pelo mobile, adicione colunas conforme cresce -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">

<!-- Padrões clássicos -->
<div class="flex flex-col gap-6 md:flex-row">       empilha → lado a lado
<h1  class="text-3xl md:text-5xl">                  tipografia que escala
<nav class="hidden md:flex">                        menu desktop
<button class="md:hidden"></button>              hambúrguer mobile
<section class="px-4 py-12 md:px-8 md:py-20">      respiro que cresce

Intervalos e max-width

faixas específicas
md:max-lg:flex     → só ENTRE md e lg
max-md:hidden      → só ABAIXO de md (esconde no mobile)

Container queries (recurso moderno — v4 nativo)

Media queries olham para a janela; container queries olham para o elemento pai. Essencial para componentes reutilizáveis que aparecem tanto numa sidebar estreita quanto no conteúdo largo:

container queries
<div class="@container">
  <article class="flex flex-col @md:flex-row @md:gap-6">
    <!-- vira horizontal quando o CONTAINER tem ≥ 28rem, não a tela -->
  </article>
</div>
Diferencial em entrevistaCitar container queries (@container/@md:) como estratégia para design systems demonstra atualização — muita gente do mercado ainda só conhece breakpoints de viewport.

Exercícios — Capítulo 9

  1. Monte um hero: no mobile, texto em cima e imagem embaixo; no lg:, lado a lado com o texto ocupando 50%.
  2. Crie uma navbar com links visíveis no desktop e botão hambúrguer no mobile (só o visual, sem JS).
  3. Desafio: refaça o card do exercício com @container em vez de md: e teste-o dentro de um pai estreito e de um pai largo.
Ver gabarito (ex. 1 — estrutura)
<section class="mx-auto grid max-w-6xl grid-cols-1 items-center
                gap-10 px-4 py-16 lg:grid-cols-2">
  <div>
    <h1 class="text-4xl font-bold lg:text-6xl">Título</h1>
    <p class="mt-4 text-lg text-slate-600">Descrição…</p>
  </div>
  <img class="w-full rounded-2xl" src="hero.jpg" alt="…">
</section>
módulo 3 · capítulo 10

Estados: hover, focus, group, peer

Estados são prefixos empilháveis — a mesma lógica dos breakpoints:

estados básicos
hover:bg-sky-600          focus:ring-2
active:scale-95           disabled:opacity-50 disabled:cursor-not-allowed
focus-visible:ring-2      → foco só via teclado (preferível a focus:)
first:rounded-t-lg  last:border-0  odd:bg-slate-50   → pseudo-classes estruturais
placeholder:text-slate-400   selection:bg-sky-200
before:content-[''] after:content-['→']              → pseudo-elementos
botão profissional completo
<button class="rounded-lg bg-sky-600 px-4 py-2 font-semibold text-white
               transition hover:bg-sky-700 active:scale-[.98]
               focus-visible:outline-none focus-visible:ring-2
               focus-visible:ring-sky-500 focus-visible:ring-offset-2
               disabled:opacity-50 disabled:pointer-events-none">
  Salvar alterações
</button>
O que diferencia um júnior contratávelBotões com focus-visible:ring e disabled: tratados. Em code review, esquecer estados de foco é apontado como falha de acessibilidade — trate isso como parte do "definition of done" de qualquer elemento interativo.

group: estilizar filhos a partir do estado do pai

group
<a href="#" class="group block rounded-xl border p-5 hover:border-sky-400">
  <h3 class="font-semibold group-hover:text-sky-600">Título do card</h3>
  <span class="inline-block transition group-hover:translate-x-1"></span>
</a>

Quando o mouse passa no <a> (que tem group), o título muda de cor e a seta desliza. Com grupos aninhados, nomeie: group/card no pai e group-hover/card: no filho.

peer: estilizar com base no estado de um irmão anterior

peer — validação sem JavaScript
<input type="email" class="peer rounded-lg border px-3 py-2" placeholder="seu@email.com">
<p class="invisible text-sm text-rose-600 peer-invalid:visible">
  Informe um e-mail válido.
</p>

O parágrafo de erro aparece quando o input (o peer) está :invalid — zero JavaScript. Também é a base de toggles customizados (peer-checked:).

Exercícios — Capítulo 10

  1. Crie uma tabela com odd:bg-slate-50 nas linhas e hover destacando a linha inteira.
  2. Faça um card de produto onde a imagem dá group-hover:scale-105 (com overflow-hidden no wrapper).
  3. Construa um toggle switch usando checkbox invisível + peer-checked:translate-x-5 peer-checked:bg-sky-500.
Ver gabarito (ex. 3)
<label class="inline-flex cursor-pointer items-center gap-3">
  <input type="checkbox" class="peer sr-only">
  <span class="relative h-6 w-11 rounded-full bg-slate-300
               transition peer-checked:bg-sky-500
               after:absolute after:left-0.5 after:top-0.5
               after:size-5 after:rounded-full after:bg-white
               after:shadow after:transition
               peer-checked:after:translate-x-5"></span>
  <span class="text-sm">Receber notificações</span>
</label>
módulo 3 · capítulo 11

Dark mode

O prefixo dark: aplica estilos no modo escuro. Por padrão ele segue a preferência do sistema (prefers-color-scheme); a maioria dos produtos usa a estratégia de classe/seletor para dar ao usuário um botão de troca.

index.css — ativando o modo por seletor (v4)
@import "tailwindcss";

/* dark: passa a responder à classe .dark no html */
@custom-variant dark (&:where(.dark, .dark *));
componente com dark mode
<div class="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm
            dark:border-slate-700 dark:bg-slate-800">
  <h3 class="font-semibold text-slate-900 dark:text-white">Faturamento</h3>
  <p class="text-slate-500 dark:text-slate-400">Últimos 30 dias</p>
</div>
toggle mínimo em JS (com persistência)
document.querySelector('#theme-btn').addEventListener('click', () => {
  const dark = document.documentElement.classList.toggle('dark');
  localStorage.theme = dark ? 'dark' : 'light';
});

// no <head>, antes do CSS, para evitar "flash" do tema errado:
if (localStorage.theme === 'dark' ||
   (!('theme' in localStorage) && matchMedia('(prefers-color-scheme: dark)').matches)) {
  document.documentElement.classList.add('dark');
}

Boas práticas de dark mode que o mercado espera

  • Não use preto puro: fundos em slate-900/950, superfícies em slate-800.
  • Reduza contraste de texto secundário: dark:text-slate-400, não branco.
  • Sombras somem no escuro — diferencie superfícies com bordas (dark:border-slate-700).
  • Nível avançado: defina tokens semânticos (bg-surface, text-muted) via variáveis CSS que mudam no .dark — assim os componentes nem precisam do prefixo (cap. 13).

Exercícios — Capítulo 11

  1. Adapte o card de dashboard acima criando também uma variação de badge e botão para dark.
  2. Implemente o toggle com persistência no seu playground.
  3. Desafio: crie variáveis --color-surface e --color-text que mudam dentro de .dark e use bg-(--color-surface) no componente.
módulo 3 · capítulo 12

Transições e animações

Transições

transition
transition             → propriedades comuns (colors, opacity, shadow, transform)
transition-colors      → só cores (mais performático, prefira quando possível)
duration-300 delay-150 ease-in-out ease-out
transition-all         → evite: transiciona tudo, inclusive layout

Transforms

transform
scale-105  -scale-x-100        rotate-45  -rotate-6
translate-x-2  -translate-y-1  skew-x-3
hover:scale-105 hover:-translate-y-1   → o "lift" clássico de cards
Passe o mouse aqui — hover:-translate-y-1 hover:shadow-xl

Animações prontas e customizadas

animate-*
animate-spin      → loaders
animate-pulse     → skeletons de carregamento
animate-bounce    → indicadores "role para baixo"
animate-ping      → pontos de notificação "ao vivo"
skeleton loader — padrão de mercado
<div class="animate-pulse space-y-3">
  <div class="h-4 w-3/4 rounded bg-slate-200"></div>
  <div class="h-4 w-1/2 rounded bg-slate-200"></div>
  <div class="h-32 rounded-xl bg-slate-200"></div>
</div>
animação própria no v4 (@theme)
@theme {
  --animate-fade-up: fade-up .5s ease-out both;
  @keyframes fade-up {
    from { opacity: 0; transform: translateY(12px); }
    to   { opacity: 1; transform: none; }
  }
}
/* uso: class="animate-fade-up" */
Acessibilidade: reduced motionRespeite quem desativa animações: motion-safe:animate-fade-up só anima se o usuário permitir, e motion-reduce:transition-none desliga transições. Mencionar isso em entrevista sinaliza maturidade.

Exercícios — Capítulo 12

  1. Crie um botão de loading: spinner com animate-spin (um SVG ou uma div com border-2 border-white/30 border-t-white rounded-full).
  2. Monte o skeleton de um card de perfil (avatar redondo + duas linhas).
  3. Crie a animação fade-up no seu projeto e aplique nos cards de uma galeria com motion-safe:.
módulo 4 · capítulo 13

Design tokens e @theme (v4)

No v4, o tema é definido no próprio CSS com @theme. Cada variável que você declara vira, automaticamente, classes utilitárias + uma variável CSS nativa. É assim que se implementa a identidade visual de uma empresa.

src/index.css — tema de marca completo
@import "tailwindcss";

@theme {
  /* Cores da marca → gera bg-brand-500, text-brand-500, border-brand-500… */
  --color-brand-500: oklch(0.68 0.16 240);
  --color-brand-600: oklch(0.60 0.16 240);

  /* Tokens SEMÂNTICOS — o nível profissional */
  --color-surface: #ffffff;
  --color-muted:   #64748b;

  /* Tipografia → gera font-display */
  --font-display: "Sora", sans-serif;

  /* Breakpoint extra → gera 3xl: */
  --breakpoint-3xl: 120rem;

  /* Espaçamento, raios, sombras… tudo é token */
  --radius-card: 1.25rem;
}
uso
<button class="bg-brand-500 hover:bg-brand-600 font-display rounded-card">
  Comprar agora
</button>

Sobrescrever vs estender

controle fino do tema
@theme {
  --color-brand-500: #0ea5e9;   /* ADICIONA à paleta padrão */
}

@theme {
  --color-*: initial;           /* APAGA todas as cores padrão…      */
  --color-brand: #0ea5e9;       /* …e recomeça só com as da empresa. */
  --color-ink:   #0b1220;       /* Padrão de design systems fechados. */
}

Tokens como variáveis CSS em runtime

Todo token vira variável nativa (var(--color-brand-500)), utilizável em CSS puro, styled-components, canvas, gráficos — a ponte entre Tailwind e o resto do stack:

interoperabilidade
/* em qualquer CSS ou lib de gráficos: */
.chart-line { stroke: var(--color-brand-500); }

<!-- e no HTML dá para consumir variável direto: -->
<div class="bg-(--color-surface) text-(--color-muted)">
No v3 (legado que você vai encontrar)Tudo isso vivia no tailwind.config.js, em theme.extend (estender) ou theme direto (sobrescrever). Saiba ler os dois formatos: manutenção de projetos v3 é realidade em consultorias e produtos maduros.

Exercícios — Capítulo 13

  1. Crie um tema com --color-brand-* em 3 tons e aplique em botão, link e badge.
  2. Crie os tokens semânticos surface/muted e faça-os mudar no .dark (variável redefinida dentro do seletor) — o componente não deve precisar de dark:.
  3. Adicione --font-display com uma fonte do Google Fonts e use no h1 do seu projeto.
módulo 4 · capítulo 14

@apply, @layer e componentização

@apply: o recurso mais mal usado do Tailwind

como funciona
@layer components {
  .btn-primary {
    @apply rounded-lg bg-sky-600 px-4 py-2 font-semibold text-white hover:bg-sky-700;
  }
}

Funciona — mas a orientação oficial (e a prática das melhores equipes) é usar com muita parcimônia. Se você recria .btn, .card, .input com @apply, você reconstruiu o CSS tradicional por cima do Tailwind e perdeu as vantagens (co-localização, remoção automática, refactor seguro).

A resposta certa: componentes de framework

Button.jsx — a repetição vive AQUI
export function Button({ children, ...props }) {
  return (
    <button
      className="rounded-lg bg-sky-600 px-4 py-2 font-semibold text-white
                 transition hover:bg-sky-700 focus-visible:ring-2"
      {...props}
    >
      {children}
    </button>
  );
}

As classes existem em um único arquivo; o resto do app usa <Button>. Esta é a resposta esperada quando perguntarem "como você evita repetição de classes?".

Quando @apply é aceitável

  • Estilizar HTML que você não controla (CMS, markdown renderizado, libs de terceiros).
  • Meia dúzia de padrões globais minúsculos em projetos sem framework de componentes.

@layer e CSS próprio convivendo com Tailwind

camadas (v4 usa cascade layers nativas)
@layer base {
  /* resets e defaults de tags */
  h1 { text-wrap: balance; }
}
@layer components {
  /* classes de componente (raras!) */
}
@utility scrollbar-none {
  /* v4: cria um utilitário DE VERDADE (funciona com hover:, md:, etc.) */
  scrollbar-width: none;
  &::-webkit-scrollbar { display: none; }
}
@utility > @applyNo v4, se você precisa de algo reutilizável que o Tailwind não tem, crie um utilitário com @utility — ele ganha suporte a variantes (md:scrollbar-none) e entra na engrenagem do framework, coisa que uma classe solta não faz.

Exercícios — Capítulo 14

  1. Crie um componente <Badge> em React que aceita a prop variant ("success" | "error" | "warning") trocando cores via objeto de mapeamento.
  2. Crie o utilitário @utility text-shadow-soft e use com hover:.
  3. Argumente em 5 linhas (como se fosse um comentário de code review) por que um PR cheio de novas classes @apply deveria ser repensado.
módulo 4 · capítulo 15

Valores e variantes arbitrárias, has(), data-*

Valores arbitrários: colchetes

quando a escala não tem o valor exato
w-[347px]  top-[117px]  text-[13px]
bg-[#1da1f2]                       → cor exata da marca de terceiros
grid-cols-[240px_1fr_auto]         → underscores viram espaços
w-[calc(100%-4rem)]
mask-[url(#mask)]
bg-(--minha-var)                   → atalho v4 para var(--minha-var)
Use como exceção, não como regraSe o seu código está cheio de [13px] e [#3b5df2], o problema é o tema: promova esses valores a tokens no @theme. Em code review, excesso de valores arbitrários é sinal de design system mal configurado.

Propriedades e variantes arbitrárias

o escape hatch completo
[mask-type:luminance]              → propriedade que o Tailwind não cobre
[&:nth-child(3)]:text-rose-500     → seletor arbitrário no próprio elemento
[&>li]:mt-2                        → estilizar filhos diretos
supports-[backdrop-filter]:bg-white/60   → @supports

data-* e aria-*: o padrão das bibliotecas headless

Radix, Headless UI e afins expõem estado via atributos — e o Tailwind estiliza direto por eles. É o vocabulário do front-end moderno:

estado via atributo
<!-- um item de accordion do Radix, por exemplo -->
<div data-state="open"
     class="data-[state=open]:bg-sky-50 data-[state=open]:border-sky-300">

<!-- acessibilidade estilizável -->
<a aria-current="page" class="aria-[current=page]:font-bold aria-[current=page]:text-sky-600">

<!-- shorthands prontos: aria-checked:, aria-disabled:, aria-expanded:… -->
<button class="aria-expanded:rotate-180"></button>

has(): estilizando o pai pelo filho

o :has() do CSS moderno, em utilitário
<!-- card de opção que se destaca quando o radio interno é marcado -->
<label class="rounded-xl border p-4
              has-checked:border-sky-500 has-checked:bg-sky-50">
  <input type="radio" name="plano" class="sr-only">
  Plano Pro — R$ 49/mês
</label>

<!-- formulário que reage a QUALQUER input inválido dentro dele -->
<form class="has-[input:invalid]:border-rose-400"></form>

<!-- negação: not-* -->
<div class="not-first:mt-6"></div>

Exercícios — Capítulo 15

  1. Crie um seletor de planos (3 labels com radios ocultos) usando has-checked: — sem nenhum JavaScript.
  2. Estilize uma navegação onde o link ativo recebe aria-current="page" e destaca-se via aria-[current=page]:.
  3. Use [&>li]: para estilizar todos os itens de uma lista a partir do <ul>, sem tocar nos <li>.
módulo 4 · capítulo 16

Plugins oficiais e customizados

Os plugins que o mercado usa

PluginO que fazQuando usar
@tailwindcss/typographyClasse prose que formata HTML "cru" (blog, CMS, markdown) com tipografia bonitaBlogs, docs, conteúdo de editor rich-text
@tailwindcss/formsReset consistente de inputs, selects, checkboxes entre navegadoresQualquer app com formulários (menos necessário no v4, que melhorou os defaults)
tailwindcss-animate / tw-animate-cssAnimações de entrada/saída prontasBase do shadcn/ui
instalando plugin no v4
/* index.css */
@import "tailwindcss";
@plugin "@tailwindcss/typography";
o plugin typography em ação
<article class="prose prose-slate lg:prose-lg dark:prose-invert">
  <!-- h1, p, ul, blockquote, code… tudo formatado automaticamente -->
  {conteudoDoCMS}
</article>

Escrevendo seu próprio "plugin" no v4

Para a maioria dos casos, @utility e @custom-variant substituem plugins JS:

utilitários e variantes próprios
/* um utilitário com valor dinâmico */
@utility text-stroke-* {
  -webkit-text-stroke-width: --value(integer)px;
}
/* uso: text-stroke-2 */

/* uma variante própria */
@custom-variant hocus (&:hover, &:focus-visible);
/* uso: hocus:bg-sky-600 → aplica em hover E focus de uma vez */

Plugins JS (plugin(function({ addUtilities, matchVariant }) {...})) seguem existindo para lógica complexa e distribuição via npm — vale conhecer a assinatura para ler código de terceiros.

Exercícios — Capítulo 16

  1. Instale o plugin typography e renderize um markdown convertido com prose dark:prose-invert.
  2. Crie a variante hocus e refatore os botões dos capítulos anteriores para usá-la.
  3. Crie um utilitário @utility glass (blur + fundo translúcido + borda) e aplique no header do seu playground.
módulo 5 · capítulo 17

Tailwind + React/Next.js: clsx, cva, tailwind-merge, shadcn/ui

Este capítulo é o que separa "sei Tailwind" de "sei Tailwind do jeito que as empresas usam". O stack dominante nas vagas: React/Next.js + Tailwind + shadcn/ui, com três micro-bibliotecas que aparecem em praticamente todo codebase profissional.

1. clsx — classes condicionais legíveis

clsx
import clsx from 'clsx';

<div className={clsx(
  'rounded-lg p-4',                    // sempre
  isActive && 'bg-sky-50 border-sky-300', // condicional
  { 'opacity-50': disabled }            // forma de objeto
)}>

2. tailwind-merge — resolvendo conflitos de classe

Problema clássico: um componente tem p-4 e alguém passa className="p-6" por prop. No CSS, quem vence é indefinido (depende da ordem no stylesheet, não no atributo!). O twMerge resolve deterministicamente: a última classe conflitante vence.

o utilitário cn() — presente em quase todo projeto
// lib/utils.ts — este arquivo existe em milhares de codebases
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs) {
  return twMerge(clsx(inputs));
}

// componente flexível e seguro:
function Card({ className, ...props }) {
  return <div className={cn('rounded-2xl border bg-white p-4', className)} {...props} />;
}
// <Card className="p-6" /> → p-6 vence o p-4, como esperado ✔

3. cva — variantes de componente declarativas

class-variance-authority — o padrão do shadcn/ui
import { cva } from 'class-variance-authority';

const button = cva(
  // base — vale para todas as variantes
  'inline-flex items-center justify-center rounded-lg font-semibold transition focus-visible:ring-2',
  {
    variants: {
      intent: {
        primary:   'bg-sky-600 text-white hover:bg-sky-700',
        secondary: 'border border-slate-300 hover:bg-slate-50',
        danger:    'bg-rose-600 text-white hover:bg-rose-700',
      },
      size: {
        sm: 'h-8 px-3 text-sm',
        md: 'h-10 px-4',
        lg: 'h-12 px-6 text-lg',
      },
    },
    defaultVariants: { intent: 'primary', size: 'md' },
  }
);

function Button({ intent, size, className, ...props }) {
  return <button className={cn(button({ intent, size }), className)} {...props} />;
}

// uso: <Button intent="danger" size="sm">Excluir</Button>

shadcn/ui: o "design system inicial" do mercado

  • Não é uma biblioteca instalada — os componentes são copiados para o seu projeto (npx shadcn@latest add button), construídos com Radix (acessibilidade) + Tailwind + cva.
  • Você é dono do código: customiza cores no tema, ajusta o que quiser, sem lutar contra estilos de terceiros.
  • É a base de uma fração enorme dos projetos novos em React — em entrevistas para vagas com Tailwind, conte como certo que shadcn/ui vai aparecer na conversa.
Organizando classes longas em JSXTrês táticas aceitas: (1) deixe o prettier-plugin-tailwindcss ordenar e confie no wrap do editor; (2) quebre em constantes nomeadas (const cardStyles = "..."); (3) extraia sub-componentes. O que NÃO fazer: template strings com interpolação parcial de classe (bg-${cor}-500) — o compilador não detecta classes dinâmicas e elas somem do build. Sempre escreva nomes de classe completos.

Exercícios — Capítulo 17

  1. Implemente o utilitário cn() e um <Card> que aceita className; prove o conflito p-4 vs p-6 com e sem twMerge.
  2. Construa o <Button> com cva acima e adicione a variante ghost e o modificador booleano fullWidth.
  3. Inicie um projeto com shadcn/ui, adicione button, card e dialog, e customize a cor primária do tema.
módulo 5 · capítulo 18

Padrões profissionais e acessibilidade

Checklist de code review (o que seniors olham)

  • Ordem de classes consistente — automatize com prettier-plugin-tailwindcss.
  • Escala respeitada — valores arbitrários só com justificativa; repetiu, vira token.
  • Estados completos — hover, focus-visible, disabled, loading em tudo que é clicável.
  • Sem classes dinâmicas quebradas — nunca text-${color}-500; use mapas de variantes.
  • Semântica primeirobutton para ação, a para navegação; div clicável é reprovação.
  • Mobile revisado de verdade — não só "não quebrou": foi pensado.

Acessibilidade com utilitários

ferramentas de a11y do Tailwind
sr-only               → visível só para leitores de tela (labels, contexto)
not-sr-only           → reverte (ex.: skip link que aparece no foco)
focus-visible:ring-2  → foco de teclado sempre visível
motion-reduce: / motion-safe:   → respeitar prefers-reduced-motion
forced-colors:        → modo de alto contraste do Windows
skip link — detalhe que impressiona em teste técnico
<a href="#conteudo"
   class="sr-only focus:not-sr-only focus:absolute focus:left-4 focus:top-4
          focus:rounded-lg focus:bg-white focus:px-4 focus:py-2 focus:shadow-lg">
  Pular para o conteúdo
</a>

Contraste de cores

Regra prática com a paleta padrão: sobre fundo branco, texto slate-500 ou mais escuro para textos pequenos (WCAG AA ≈ 4.5:1); *-400 geralmente só serve para textos grandes ou decorativos. Sobre cores saturadas (sky-500+), texto branco. Valide com o DevTools ou WebAIM Contrast Checker.

Organização de projeto que escala

estrutura típica de produto
src/
├─ index.css            ← @import "tailwindcss" + @theme (tokens da marca)
├─ lib/utils.ts         ← cn()
├─ components/
│  ├─ ui/               ← primitivos (Button, Card, Input) — shadcn vive aqui
│  └─ features/         ← componentes de domínio compostos dos primitivos
└─ app/ | pages/        ← telas montam features; quase não têm classe própria

A pirâmide: tokens → primitivos → features → páginas. Quanto mais alto, menos classes Tailwind diretas — no topo, quase tudo é composição de componentes.

Exercícios — Capítulo 18

  1. Adicione um skip link funcional ao seu projeto e teste navegando por Tab.
  2. Audite um componente antigo seu com o checklist de code review e liste 3 correções.
  3. Pegue um formulário e garanta: labels associados, focus-visible em tudo, mensagens de erro com aria-describedby.
módulo 5 · capítulo 19

Performance e produção

Como o Tailwind gera CSS pequeno

  • Compilação sob demanda (JIT): o compilador escaneia seus arquivos e gera apenas as classes encontradas. Não existe "CSS do framework" gigante para purgar — ele nunca é gerado.
  • Resultado típico: menos de 10 kB de CSS comprimido mesmo em apps grandes, porque utilitários são infinitamente reutilizados (o custo de usar flex pela milésima vez é zero).
  • v4 (engine Oxide): builds completos em milissegundos e watch quase instantâneo.

O que pode dar errado (e cai em entrevista)

a armadilha nº 1 — classes dinâmicas
// ✗ O scanner lê texto, não executa JS. Isto NÃO entra no build:
const cor = 'rose';
<div className={`bg-${cor}-500`}>

// ✔ Nomes completos, escolhidos por lógica:
const cores = { ok: 'bg-emerald-500', erro: 'bg-rose-500' };
<div className={cores[status]}>
  • Classe some em produção? 99% das vezes é interpolação dinâmica ou arquivo fora do escaneamento (no v4, verifique se não está no .gitignore; use @source para incluir pastas extras, ex.: @source "../node_modules/minha-lib").
  • CSS crítico e caching: um único arquivo CSS pequeno e estável = excelente cache entre deploys.
  • Evite transition-all e anime preferencialmente transform/opacity (compositor, sem reflow) — conhecimento de CSS performance que continua valendo.
Resposta pronta para "Tailwind não incha o HTML?""O HTML fica maior, mas gzip/brotli comprimem repetição de forma extremamente eficiente, e o CSS despenca para poucos kB estáveis e cacheáveis. No total transferido, aplicações Tailwind normalmente ficam menores que o equivalente com CSS crescendo linearmente por feature."

Exercícios — Capítulo 19

  1. Rode npm run build no seu projeto e meça o tamanho do CSS gerado.
  2. Introduza de propósito uma classe interpolada, confira que ela quebra no build de produção, e corrija com mapa de variantes.
  3. Explique por escrito (3 linhas) por que "purge" é um termo desatualizado desde o JIT.
módulo 6 · capítulo 20

Projeto 1 — Landing page responsiva

Missão: construir a landing de um SaaS fictício ("Pulso — monitoramento de sites") com navbar, hero, grade de features e footer. É o formato de teste técnico mais comum para vagas júnior. Construa por etapas, sem olhar o gabarito antes de tentar.

Etapa 1 — Navbar com efeito vidro

navbar
<header class="sticky top-0 z-40 border-b border-slate-200 bg-white/80 backdrop-blur-md">
  <nav class="mx-auto flex h-16 max-w-6xl items-center justify-between px-4">
    <a href="#" class="text-xl font-bold tracking-tight">Pulso<span class="text-sky-500">.</span></a>
    <div class="hidden items-center gap-8 text-sm font-medium text-slate-600 md:flex">
      <a class="hover:text-slate-900" href="#features">Recursos</a>
      <a class="hover:text-slate-900" href="#precos">Preços</a>
      <a class="rounded-lg bg-slate-900 px-4 py-2 text-white transition hover:bg-slate-700" href="#">Começar grátis</a>
    </div>
    <button class="md:hidden" aria-label="Abrir menu"></button>
  </nav>
</header>

Etapa 2 — Hero com badge, gradiente no título e CTAs

hero
<section class="mx-auto max-w-6xl px-4 py-20 text-center md:py-32">
  <span class="inline-flex items-center gap-2 rounded-full border border-sky-200
               bg-sky-50 px-3 py-1 text-xs font-medium text-sky-700">
    <span class="relative flex size-2">
      <span class="absolute inline-flex size-full animate-ping rounded-full bg-sky-400 opacity-75"></span>
      <span class="relative inline-flex size-2 rounded-full bg-sky-500"></span>
    </span>
    Novo: alertas via WhatsApp
  </span>

  <h1 class="mx-auto mt-6 max-w-3xl text-balance text-4xl font-bold tracking-tight md:text-6xl">
    Saiba que seu site caiu
    <span class="bg-gradient-to-r from-sky-500 to-indigo-500 bg-clip-text text-transparent">
      antes do seu cliente
    </span>
  </h1>

  <p class="mx-auto mt-6 max-w-xl text-lg text-slate-600">
    Monitoramento a cada 30 segundos, alertas instantâneos e relatórios de uptime.
  </p>

  <div class="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
    <a class="w-full rounded-xl bg-sky-600 px-6 py-3 font-semibold text-white shadow-lg
              shadow-sky-500/30 transition hover:bg-sky-700 sm:w-auto" href="#">
      Começar grátis
    </a>
    <a class="w-full rounded-xl border border-slate-300 px-6 py-3 font-semibold
              transition hover:bg-slate-50 sm:w-auto" href="#">
      Ver demonstração
    </a>
  </div>
</section>

Etapa 3 — Grade de features

features
<section id="features" class="bg-slate-50 py-20">
  <div class="mx-auto max-w-6xl px-4">
    <h2 class="text-center text-3xl font-bold">Tudo o que você precisa</h2>
    <div class="mt-12 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
      <!-- repita este card 6× -->
      <article class="group rounded-2xl border border-slate-200 bg-white p-6
                     transition hover:-translate-y-1 hover:shadow-lg">
        <div class="grid size-11 place-items-center rounded-xl bg-sky-100
                    text-xl transition group-hover:bg-sky-500"></div>
        <h3 class="mt-4 font-semibold">Checagens a cada 30s</h3>
        <p class="mt-2 text-sm leading-relaxed text-slate-600">
          Monitoramento contínuo de 12 regiões do mundo.
        </p>
      </article>
    </div>
  </div>
</section>

Complete o projeto

  1. Adicione uma seção de preços com 3 planos, destacando o do meio com ring-2 ring-sky-500 scale-105 e um badge "Mais popular" posicionado com absolute -top-3.
  2. Adicione um footer em bg-slate-900 com 4 colunas (grid sm:grid-cols-2 lg:grid-cols-4) que empilham no mobile.
  3. Ative dark mode em toda a página.
  4. Bônus de portfólio: publique no Vercel/Netlify e coloque o link no seu GitHub — projeto simples publicado vale mais que projeto complexo no localhost.
módulo 6 · capítulo 21

Projeto 2 — Card de dashboard com dark mode

Missão: um card de métrica de dashboard SaaS — o componente que mais aparece em testes para vagas de produto. Requisitos: número grande, comparação com período anterior (positiva/negativa), mini-lista, dark mode completo e estado de loading.

MetricCard — versão completa
<article class="w-full max-w-sm rounded-2xl border border-slate-200 bg-white p-6
               shadow-sm dark:border-slate-700 dark:bg-slate-800">

  <!-- cabeçalho -->
  <div class="flex items-center justify-between">
    <h3 class="text-sm font-medium text-slate-500 dark:text-slate-400">
      Receita mensal
    </h3>
    <button class="rounded-lg p-1.5 text-slate-400 transition hover:bg-slate-100
                   hover:text-slate-600 dark:hover:bg-slate-700"
            aria-label="Opções"></button>
  </div>

  <!-- métrica -->
  <div class="mt-2 flex items-baseline gap-2">
    <span class="text-3xl font-bold tabular-nums tracking-tight
                 text-slate-900 dark:text-white">R$ 48.290</span>
    <span class="inline-flex items-center gap-0.5 rounded-full bg-emerald-100
                 px-2 py-0.5 text-xs font-semibold text-emerald-700
                 dark:bg-emerald-500/15 dark:text-emerald-400">
      ↑ 12,4%
    </span>
  </div>
  <p class="mt-1 text-xs text-slate-400">vs. mês anterior</p>

  <!-- mini-lista com divide -->
  <ul class="mt-5 divide-y divide-slate-100 text-sm dark:divide-slate-700">
    <li class="flex items-center justify-between py-2">
      <span class="text-slate-600 dark:text-slate-300">Plano Pro</span>
      <span class="font-medium tabular-nums dark:text-white">R$ 31.480</span>
    </li>
    <li class="flex items-center justify-between py-2">
      <span class="text-slate-600 dark:text-slate-300">Plano Starter</span>
      <span class="font-medium tabular-nums dark:text-white">R$ 16.810</span>
    </li>
  </ul>
</article>
Receita mensal
R$ 48.290↑ 12,4%
vs. mês anterior
Plano ProR$ 31.480
Plano StarterR$ 16.810
Detalhes que fazem parecer sêniortabular-nums alinha dígitos em colunas de números (essencial em dashboards); badges no dark usam bg-emerald-500/15 (cor com opacidade) em vez de tons sólidos — fica elegante em qualquer fundo; o botão "⋯" tem aria-label.

Complete o projeto

  1. Crie a variação negativa (↓ 3,2% em rose) e transforme em componente React com props title, value, delta.
  2. Crie a versão skeleton do card inteiro com animate-pulse.
  3. Monte um grid de 4 cards responsivo (grid-cols-1 sm:grid-cols-2 xl:grid-cols-4) com dados diferentes.
módulo 6 · capítulo 22

Projeto 3 — Formulário acessível com validação visual

Missão: formulário de cadastro com validação nativa estilizada — sem JavaScript de validação. Formulários são onde acessibilidade e Tailwind se encontram, e onde muitos candidatos escorregam.

campo de formulário — o padrão completo
<form class="mx-auto max-w-md space-y-5 rounded-2xl border border-slate-200 bg-white p-8 shadow-sm">
  <h2 class="text-2xl font-bold">Criar conta</h2>

  <div>
    <label for="email" class="mb-1.5 block text-sm font-medium text-slate-700">
      E-mail
    </label>
    <input id="email" type="email" required aria-describedby="email-erro"
      class="peer w-full rounded-lg border border-slate-300 px-3 py-2
             placeholder:text-slate-400
             focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-500/30
             invalid:[&:not(:placeholder-shown)]:border-rose-400"
      placeholder="voce@empresa.com">
    <p id="email-erro"
       class="mt-1.5 hidden text-sm text-rose-600
              peer-[&:invalid:not(:placeholder-shown)]:block">
      Informe um e-mail válido.
    </p>
  </div>

  <!-- checkbox de termos -->
  <label class="flex items-start gap-3 text-sm text-slate-600">
    <input type="checkbox" required
           class="mt-0.5 size-4 rounded border-slate-300 accent-sky-600">
    Li e aceito os <a class="font-medium text-sky-600 underline-offset-2 hover:underline" href="#">termos de uso</a>.
  </label>

  <button class="w-full rounded-lg bg-sky-600 py-2.5 font-semibold text-white transition
                 hover:bg-sky-700 focus-visible:ring-2 focus-visible:ring-sky-500
                 focus-visible:ring-offset-2 disabled:opacity-50">
    Criar conta
  </button>
</form>

A mágica do erro: invalid:[&:not(:placeholder-shown)] só marca o campo como errado depois que o usuário digitou algo (evita o formulário nascer todo vermelho), e o peer revela a mensagem correspondente.

Complete o projeto

  1. Adicione campo de senha com dica de requisitos que fica text-emerald-600 quando o padrão (pattern ou minlength) é satisfeito, via peer-valid:.
  2. Adicione um <select> de "como nos conheceu" estilizado de forma consistente com os inputs.
  3. Teste o formulário inteiro só com teclado (Tab/Enter) e com o leitor de tela do sistema; corrija o que travar.
  4. Versão dark de tudo.
módulo 6 · capítulo 23

Perguntas de entrevista (com respostas)

As perguntas reais mais frequentes sobre Tailwind em processos de front-end. Tente responder em voz alta antes de abrir cada resposta.

O que é utility-first e quais as vantagens sobre CSS tradicional/BEM?
Compor interfaces com classes de propósito único direto na marcação, em vez de classes semânticas com CSS separado. Vantagens: velocidade (sem trocar de arquivo nem inventar nomes), consistência via escala de design, remoção segura de código (estilo morre com o componente) e CSS final minúsculo. O custo — marcação verbosa — é mitigado por componentes de framework, onde cada conjunto de classes existe uma única vez.
Tailwind não é a mesma coisa que estilos inline?
Não. Estilos inline não suportam estados (hover:, focus:), media queries (md:), pseudo-elementos (before:), dark mode, nem restringem valores a uma escala. Utilitários são classes reais com especificidade normal, geradas a partir de um design system configurável — inline styles são valores mágicos soltos.
Como você evita repetição de classes em um projeto grande?
Extraindo componentes (React/Vue/Blade), não classes CSS. A repetição visual vive em um <Button> ou <Card> com variantes gerenciadas por cva ou mapas de objeto. @apply só para HTML que não controlo (markdown de CMS, por exemplo). Complemento: tokens no @theme para valores repetidos.
Por que `bg-${cor}-500` não funciona em produção?
O compilador escaneia os arquivos como texto procurando nomes de classe completos — ele não executa JavaScript. Classes montadas por interpolação nunca aparecem inteiras no código-fonte, então não são geradas. Solução: mapas com nomes completos ({ ok: 'bg-emerald-500', erro: 'bg-rose-500' }).
Para que servem tailwind-merge e clsx? Por que quase todo projeto tem um cn()?
clsx monta strings de classe condicionalmente com sintaxe limpa. tailwind-merge resolve conflitos entre utilitários (ex.: p-4 da base vs p-6 vindo por prop), garantindo que a última vença — sem ele, o resultado dependeria da ordem no CSS gerado, que é imprevisível. cn() combina os dois e é o padrão para componentes que aceitam className.
Como funciona a responsividade no Tailwind?
Mobile-first: classes sem prefixo valem para todas as larguras; prefixos (sm: md: lg:) aplicam de um min-width para cima. Também há max-md: para "abaixo de", intervalos (md:max-lg:) e, no v4, container queries nativas (@container + @md:) que respondem ao tamanho do elemento pai — ideais para design systems.
O que mudou do Tailwind v3 para o v4?
Configuração migrou para o CSS (@theme em vez de tailwind.config.js, que segue suportado); @import "tailwindcss" substituiu as três diretivas; detecção automática de conteúdo (sem array content); novo engine em Rust com builds muito mais rápidos; todos os tokens viram variáveis CSS nativas; cores em OKLCH; container queries e @utility/@custom-variant nativos.
Como você implementaria dark mode em um produto real?
Estratégia de seletor (classe .dark no html) para dar controle ao usuário, com script no head lendo localStorage/preferência do sistema antes do primeiro paint (evita flash). Nos componentes, dark: nos pontos de cor — ou, em escala, tokens semânticos (surface/muted) via variáveis CSS redefinidas no .dark, deixando os componentes agnósticos ao tema.
O HTML cheio de classes não prejudica a performance?
O HTML cresce, mas compressão (gzip/brotli) lida extremamente bem com repetição. Em troca, o CSS despenca para poucos kB, estável entre deploys (cache ótimo). No agregado, o peso transferido tende a ser menor que em abordagens onde o CSS cresce a cada feature.
Quando você usaria @apply?
Quase nunca: só para estilizar HTML fora do meu controle (saída de CMS/markdown — embora o plugin typography cubra a maior parte) ou meia dúzia de padrões globais em projetos sem componentes. Usar @apply para recriar .btn/.card em geral reintroduz os problemas que o utility-first resolve. No v4, para utilitários novos reutilizáveis, o correto é @utility, que ganha suporte a variantes.
Como estilizar componentes de bibliotecas headless (Radix/Headless UI)?
Elas expõem estado via atributos data-*/aria-*, e o Tailwind estiliza direto: data-[state=open]:rotate-180, aria-expanded:bg-sky-50. É o padrão do shadcn/ui: Radix cuida de comportamento e acessibilidade; Tailwind + cva cuidam do visual.
Pergunta prática comum: "Recebeu um Figma. Qual seu processo até o PR?"
1) Mapear tokens do design (cores, fontes, raios) para o @theme; 2) HTML semântico da estrutura; 3) layout de fora para dentro (containers → grid/flex → espaçamento), mobile-first; 4) tipografia e cores; 5) estados e acessibilidade (focus-visible, aria); 6) dark mode se aplicável; 7) conferir com o design sobreposto, rodar prettier para ordenar classes, revisar responsivo real antes do PR.
No teste ao vivoVerbalize o raciocínio ("vou de mobile-first, uma coluna, e no lg divido em grid"), consulte a doc sem medo (todo mundo consulta) e priorize entregar funcional-acessível sobre pixel-perfect incompleto.
módulo 6 · capítulo 24

Cheatsheet de referência

Cole na segunda tela. Padrão: 1 unidade = 0.25rem = 4px.

Espaçamento
p-4 px-4 py-4 pt-4 padding
m-4 mx-auto -mt-2 margin
gap-4 gap-x-2 gap
space-y-4 entre filhos
ps-4 pe-4 lógicos (RTL)
Tamanho
w-full w-1/2 w-64 largura
max-w-6xl min-h-screen
size-10 w+h
h-dvh altura mobile-safe
Tipografia
text-sm…text-9xl tamanho
font-medium/semibold/bold
leading-relaxed tracking-tight
truncate line-clamp-3
uppercase italic text-balance
Cores
text- bg- border- ring-
slate/sky/emerald/rose…
tons 50→950
bg-sky-500/60 opacidade
bg-(--var) variável CSS
Flex
flex flex-col flex-wrap
items-center justify-between
flex-1 grow shrink-0
self-end order-first
truncate em flex: min-w-0
Grid
grid grid-cols-3
col-span-2 row-span-2
grid-cols-[240px_1fr]
place-items-center
Posição
relative absolute fixed sticky
inset-0 top-0 -top-1
z-10 z-50
overflow-hidden overflow-y-auto
Borda & sombra
border border-2 rounded-lg
rounded-full divide-y
ring-2 ring-offset-2
shadow-md shadow-sky-500/30
Responsivo
sm:640 md:768 lg:1024
xl:1280 2xl:1536
max-md:hidden md:max-lg:flex
@container @md:flex-row
Estados
hover: focus-visible: active:
disabled: checked: invalid:
first: last: odd: not-first:
group-hover: peer-checked:
has-checked: aria-expanded:
data-[state=open]:
Efeitos & motion
transition duration-300
hover:scale-105 -translate-y-1
animate-spin/pulse/ping
backdrop-blur-md bg-white/70
motion-safe: motion-reduce:
v4 no CSS
@import "tailwindcss"
@theme { --color-brand: … }
@utility nome { … }
@custom-variant hocus (…)
@plugin "@tailwindcss/typography"
@source "../lib"

Receitas de bolso

copy & paste honesto
/* container de página */      mx-auto max-w-6xl px-4
/* centralizar tudo */          flex min-h-dvh items-center justify-center
/* galeria responsiva */        grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3
/* card padrão */               rounded-2xl border border-slate-200 bg-white p-6 shadow-sm
/* overlay de modal */          fixed inset-0 z-50 grid place-items-center bg-black/50 p-4
/* header vidro */              sticky top-0 z-40 bg-white/80 backdrop-blur-md border-b
/* input padrão */              w-full rounded-lg border border-slate-300 px-3 py-2
                                focus:border-sky-500 focus:ring-2 focus:ring-sky-500/30
/* foco acessível */            focus-visible:outline-none focus-visible:ring-2
                                focus-visible:ring-sky-500 focus-visible:ring-offset-2
módulo 6 · capítulo 25

Roadmap: próximos passos para o mercado

Plano de 4 semanas (partindo de quem já sabe CSS)

SemanaFocoEntregável
1Módulos 1–2 (fundamentos + layout). Faça TODOS os exercícios no playground.Landing page do cap. 20 publicada
2Módulos 3–4 (estados, dark mode, tokens). Refaça a landing com @theme próprio e dark mode.Landing v2 com tema e dark
3Módulo 5 em um projeto React: cn(), cva, shadcn/ui.Mini-dashboard (cap. 21) com 4+ componentes
4Simulação: escolha um design no Figma Community e reproduza contra o relógio (4h). Revise as perguntas do cap. 23.Clone pixel-quase-perfect no portfólio

O que colocar no currículo/portfólio

  • “Tailwind CSS (v3/v4)” junto de React/Next.js — a combinação é o que as vagas pedem.
  • 2–3 projetos publicados (Vercel/Netlify) com README mostrando: responsivo, dark mode, acessibilidade, componentes com variantes.
  • Um repositório com seu "ui kit" pessoal (Button, Input, Card, Modal com cva) — demonstra pensamento de design system.

Para continuar evoluindo

  • Documentação oficial — tailwindcss.com (leia as páginas de conceito, não só a referência).
  • Refactoring UI — livro dos criadores do Tailwind sobre design para devs; muda seu nível visual.
  • shadcn/ui + Radix docs — leia o código dos componentes que instalar: é uma aula de Tailwind avançado.
  • Pratique reproduzindo interfaces reais — Linear, Stripe, Vercel: abra o DevTools e estude as escolhas.
A métrica que importaVocê está pronto para testes técnicos quando consegue transformar um screenshot em página responsiva com dark mode em poucas horas, sem consultar mais do que a doc oficial. Tudo nesta apostila converge para isso.