Skip to content

Capítulo 12 — Framework CSS: Tailwind CSS

Vídeo curto explicativo (link será adicionado posteriormente)


12.1 — O que é um framework CSS e por que usar

Vídeo curto explicativo (link será adicionado posteriormente)

Um framework CSS é um conjunto pré-construído de abstrações, convenções e ferramentas que acelera o desenvolvimento de interfaces ao fornecer soluções padronizadas para problemas recorrentes de estilização e layout. Em vez de escrever CSS do zero para cada projeto, o desenvolvedor parte de uma base consolidada — reduzindo o tempo de configuração inicial, promovendo consistência visual e beneficiando-se de decisões de design já validadas pela comunidade.

A adoção de frameworks CSS tornou-se praticamente universal no desenvolvimento front-end moderno. Compreender como e por que eles funcionam — incluindo suas limitações — é uma competência fundamental para qualquer desenvolvedor web.

12.1.1 — O problema que frameworks resolvem

O desenvolvimento de CSS em projetos de médio e grande porte enfrenta um conjunto de problemas recorrentes que frameworks foram criados para mitigar:

Inconsistência visual: sem um sistema de referência compartilhado, diferentes desenvolvedores de uma mesma equipe produzem estilos inconsistentes — margens diferentes para espaçamentos similares, botões com variações sutis de cor, tipografia sem hierarquia coerente.

Retrabalho de decisões: cada projeto recria soluções para os mesmos problemas — sistema de grid, componentes de botão, formulários, navegação responsiva. Frameworks encapsulam essas soluções, permitindo que a equipe foque no que é específico do produto.

Escalabilidade do CSS: CSS cresce sem estrutura inerente. Em projetos longos, a folha de estilos frequentemente acumula regras redundantes, conflitos de especificidade e seletores frágeis. Frameworks impõem convenções que limitam esse crescimento desordenado.

Curva de entrada para equipes: novos membros integram-se mais rapidamente quando o projeto usa um framework conhecido — as convenções são documentadas externamente, não precisam ser aprendidas a partir do código legado.

12.1.2 — Categorias de frameworks: utility-first vs component-based

Os frameworks CSS modernos se dividem em duas filosofias fundamentais:

Frameworks baseados em componentes (component-based): fornecem componentes de interface prontos — botões, cards, modais, navbars, grids — que o desenvolvedor instancia adicionando classes predefinidas ao HTML. O Bootstrap é o exemplo mais conhecido. O desenvolvedor consome componentes sem necessariamente escrever CSS.

Frameworks utility-first: fornecem classes de baixo nível (utilities) que mapeiam diretamente para propriedades CSS individuais. O desenvolvedor constrói a interface compondo essas classes no HTML, sem componentes prontos. O Tailwind CSS é o principal representante desta categoria.

<!-- Component-based (Bootstrap): componente pronto -->
<button class="btn btn-primary btn-lg">Salvar</button>

<!-- Utility-first (Tailwind): composto de utilities -->
<button class="bg-blue-700 text-white font-semibold px-6 py-3
               rounded-lg hover:bg-blue-800 transition-colors">
  Salvar
</button>

A distinção não é apenas sintática — ela reflete abordagens diferentes de controle, flexibilidade e manutenção, exploradas em profundidade na seção 12.2.

12.1.3 — Tailwind CSS no contexto do mercado

O Tailwind CSS foi criado por Adam Wathan e lançado em 2017. Em um período relativamente curto, tornou-se o framework CSS mais amplamente adotado no desenvolvimento front-end moderno — superando o Bootstrap em downloads npm pela primeira vez em 2022 e mantendo crescimento consistente desde então.

Segundo a pesquisa State of CSS 2024, o Tailwind possui taxa de satisfação superior a 80% entre desenvolvedores que o utilizam — uma das mais altas da categoria. É o framework padrão em ecossistemas como Next.js, Nuxt.js e Laravel, e é utilizado por empresas como GitHub, Shopify, OpenAI e Vercel.

A versão atual é o Tailwind CSS v4 (2025), que introduziu um novo motor de build baseado em Rust (Oxide), configuração via CSS em vez de JavaScript e melhorias significativas de desempenho. Este capítulo cobre os conceitos fundamentais que são estáveis entre versões, com exemplos baseados nas convenções do Tailwind v3/v4.

Referência: Tailwind CSS — Documentação oficial

12.1.4 — Tailwind vs Bootstrap: diferenças filosóficas e práticas

A comparação entre Tailwind e Bootstrap é inevitável — ambos dominam o mercado de frameworks CSS, mas representam filosofias opostas. Compreender as diferenças ajuda a escolher a ferramenta adequada para cada contexto.

Critério Tailwind CSS Bootstrap
Filosofia Utility-first Component-based
Componentes prontos Nenhum (apenas utilities) Extenso (botões, modais, navbars...)
Customização Alta — tudo é configurável Média — requer sobrescrever variáveis Sass
Tamanho do CSS Mínimo — apenas classes usadas (PurgeCSS) Maior — importa componentes não usados
Curva de aprendizado Maior — requer conhecer as utilities Menor — basta adicionar classes de componentes
Velocidade inicial Lenta — constrói do zero Rápida — componentes prontos
Flexibilidade visual Muito alta Limitada pelo design do Bootstrap
Aparência padrão Nenhuma "Cara de Bootstrap" reconhecível
Versão atual v4 (2025) v5.3 (2024)

Quando escolher Tailwind: projetos onde a identidade visual é importante e não se quer a aparência genérica do Bootstrap; equipes com desenvolvedores front-end experientes; produtos que evoluem rapidamente e precisam de flexibilidade.

Quando escolher Bootstrap: prototipação rápida onde a aparência visual não é crítica; sistemas internos e ferramentas administrativas onde velocidade de desenvolvimento supera customização; equipes com pouca experiência em CSS que se beneficiam de componentes prontos.

Posição deste material: este capítulo cobre o Tailwind CSS conforme o plano de ensino. Caso o projeto ou estágio exija Bootstrap, a documentação oficial (getbootstrap.com) é autoexplicativa após dominar os fundamentos de CSS dos capítulos anteriores.


12.2 — Filosofia utility-first

Vídeo curto explicativo (link será adicionado posteriormente)

12.2.1 — O que são classes utilitárias

Uma classe utilitária (utility class) é uma classe CSS com propósito único — ela aplica exatamente uma propriedade CSS com um valor específico. Em vez de criar abstrações semânticas, classes utilitárias são funcionais e diretas:

/* Classes utilitárias — cada uma faz uma coisa */
.flex        { display: flex; }
.items-center { align-items: center; }
.gap-4       { gap: 1rem; }
.text-lg     { font-size: 1.125rem; }
.font-bold   { font-weight: 700; }
.text-white  { color: #ffffff; }
.bg-blue-700 { background-color: #1d4ed8; }
.rounded-lg  { border-radius: 0.5rem; }
.px-6        { padding-left: 1.5rem; padding-right: 1.5rem; }
.py-3        { padding-top: 0.75rem; padding-bottom: 0.75rem; }

A premissa do utility-first é que interfaces são compostas adicionando essas classes diretamente ao HTML — sem criar classes semânticas intermediárias para a maioria dos casos:

<!-- CSS semântico: classe com nome descritivo, CSS separado -->
<nav class="navbar">...</nav>

<!-- Utility-first: propriedades expressas diretamente no HTML -->
<nav class="flex items-center justify-between px-6 py-4 bg-slate-900">...</nav>

12.2.2 — Utility-first vs CSS semântico: a tensão e o equilíbrio

A abordagem utility-first gera uma tensão filosófica com a separação clássica entre HTML (estrutura) e CSS (apresentação) — um dos princípios fundamentais apresentados no Capítulo 7. Ao colocar informações visuais diretamente no HTML via classes, o utility-first viola aparentemente essa separação.

Adam Wathan argumenta, em seu ensaio CSS Utility Classes and "Separation of Concerns", que a separação relevante não é entre HTML e CSS, mas entre componentes de interface reutilizáveis. Em uma aplicação moderna baseada em componentes, o HTML de um botão não é "um documento separado do CSS" — é parte de um componente coeso. A questão não é onde as classes estão, mas se o componente é reutilizável.

Críticas legítimas ao utility-first:

  • HTML verboso: classes longas reduzem a legibilidade do markup, especialmente para desenvolvedores acostumados ao CSS semântico
  • Dificuldade de manutenção em componentes repetidos: sem extração de componentes, a mesma lista de classes precisa ser duplicada em cada instância
  • Curva de aprendizado: exige memorizar ou consultar frequentemente a nomenclatura das utilities

Vantagens práticas:

  • Sem conflitos de nomenclatura: não há necessidade de inventar nomes de classes para cada elemento
  • CSS nunca cresce: o arquivo CSS final contém apenas as utilities utilizadas — não importa quantas páginas o projeto tem
  • Iteração visual rápida: mudanças visuais são feitas diretamente no HTML sem alternar entre arquivos
  • Consistência por convenção: todos os valores de espaçamento, cor e tipografia vêm da escala do Tailwind — não de valores arbitrários

12.2.3 — Comparação: CSS tradicional vs Tailwind para o mesmo componente

<!-- CSS Tradicional -->
<article class="card">
  <img class="card__imagem" src="foto.jpg" alt="Descrição" />
  <div class="card__corpo">
    <h2 class="card__titulo">Título do Card</h2>
    <p class="card__descricao">Descrição do conteúdo...</p>
  </div>
  <footer class="card__rodape">
    <a class="btn btn--primario" href="#">Saiba mais</a>
  </footer>
</article>
/* CSS separado: ~30 linhas */
.card {
  background: white;
  border-radius: 0.5rem;
  box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.card__imagem { width: 100%; height: 200px; object-fit: cover; }
.card__corpo  { flex: 1; padding: 1.5rem; }
.card__titulo { font-size: 1.25rem; font-weight: 600; margin-bottom: 0.5rem; }
/* ... */
<!-- Tailwind: tudo no HTML, ~0 linhas de CSS personalizado -->
<article class="bg-white rounded-lg shadow-md overflow-hidden flex flex-col">
  <img class="w-full h-48 object-cover" src="foto.jpg" alt="Descrição" />
  <div class="flex-1 p-6">
    <h2 class="text-xl font-semibold mb-2">Título do Card</h2>
    <p class="text-slate-600 leading-relaxed">Descrição do conteúdo...</p>
  </div>
  <footer class="px-6 pb-6">
    <a class="inline-flex items-center px-4 py-2 bg-blue-700 text-white
              font-semibold rounded-lg hover:bg-blue-800 transition-colors"
       href="#">
      Saiba mais
    </a>
  </footer>
</article>

12.2.4 — Quando utility-first faz sentido e quando não faz

Faz sentido quando: - O projeto é construído com componentes (React, Vue, templates reutilizáveis) - A equipe valoriza iteração visual rápida - A identidade visual é importante e diferenciada - O projeto precisa de CSS mínimo em produção

Não faz sentido quando: - O projeto é um documento HTML estático simples sem componentes - A equipe tem resistência à verbosidade no HTML - Há necessidade de componentes visuais prontos rapidamente (Bootstrap é mais eficiente) - O projeto usa um CMS que gera HTML e não permite controle das classes


12.3 — Instalação e configuração

Vídeo curto explicativo (link será adicionado posteriormente)

12.3.1 — Usando Tailwind via CDN (para prototipação)

A forma mais rápida de experimentar o Tailwind é via CDN — sem instalação, ideal para protótipos e atividades iniciais:

<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Tailwind via CDN</title>
  <!-- Tailwind CSS via CDN — apenas para prototipação -->
  <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-slate-50 text-slate-900 p-8">
  <h1 class="text-4xl font-bold text-blue-700 mb-4">Olá, Tailwind!</h1>
  <p class="text-lg text-slate-600">
    Esta página usa Tailwind CSS via CDN.
  </p>
</body>
</html>

⚠️ Limitação do CDN: a versão CDN do Tailwind carrega o framework inteiro no browser e processa as classes em tempo de execução — produzindo um arquivo CSS grande e sem otimização. É adequada apenas para prototipação e aprendizado. Nunca use a CDN em produção — utilize a instalação via npm com o processo de build para obter apenas as classes efetivamente usadas.

12.3.2 — Instalação via npm e CLI

Para projetos reais, o Tailwind é instalado como dependência de desenvolvimento e processado por uma ferramenta de build:

Passo 1 — Inicializar o projeto e instalar o Tailwind:

# Inicializar package.json (se ainda não existir)
npm init -y

# Instalar Tailwind CSS e suas dependências
npm install -D tailwindcss postcss autoprefixer

# Gerar os arquivos de configuração
npx tailwindcss init -p

Passo 2 — Configurar o tailwind.config.js:

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  // content: informa ao Tailwind quais arquivos escanear
  // para detectar as classes utilizadas
  content: [
    "./src/**/*.{html,js}",
    "./*.html",
  ],
  theme: {
    extend: {
      // personalizações do tema (seção 12.8)
    },
  },
  plugins: [],
}

Passo 3 — Criar o arquivo CSS de entrada:

/* src/input.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

Passo 4 — Executar o processo de build:

# Build único
npx tailwindcss -i ./src/input.css -o ./dist/output.css

# Build em modo watch (recompila ao salvar)
npx tailwindcss -i ./src/input.css -o ./dist/output.css --watch

Passo 5 — Vincular o CSS gerado ao HTML:

<head>
  <link rel="stylesheet" href="./dist/output.css" />
</head>

Scripts no package.json para facilitar:

{
  "scripts": {
    "dev": "tailwindcss -i ./src/input.css -o ./dist/output.css --watch",
    "build": "tailwindcss -i ./src/input.css -o ./dist/output.css --minify"
  }
}

Com esses scripts, npm run dev inicia o modo de desenvolvimento e npm run build gera o CSS minificado para produção.

12.3.3 — O arquivo tailwind.config.js

O arquivo de configuração é o coração da customização do Tailwind. Sua estrutura principal:

// tailwind.config.js
module.exports = {
  // Arquivos a escanear para detecção de classes
  content: ["./src/**/*.{html,js,ts,jsx,tsx}"],

  // darkMode: 'media' (sistema) ou 'class' (manual via classe .dark)
  darkMode: 'media',

  theme: {
    // theme.XXX: SUBSTITUI os valores padrão
    screens: {
      'sm': '640px',
      'md': '768px',
      'lg': '1024px',
      'xl': '1280px',
      '2xl': '1536px',
    },

    // theme.extend.XXX: ADICIONA aos valores padrão
    extend: {
      colors: {
        'primaria': {
          50:  '#eff6ff',
          500: '#3b82f6',
          700: '#1d4ed8',
          900: '#1e3a8a',
        },
      },
      fontFamily: {
        'sem-serifa': ['Inter', 'system-ui', 'sans-serif'],
      },
      spacing: {
        '18': '4.5rem',
        '88': '22rem',
      },
    },
  },

  plugins: [],
}

12.3.4 — Integração com o processo de build

Em projetos mais complexos, o Tailwind se integra com ferramentas de build como Vite, Webpack ou Parcel. A integração com Vite — o bundler mais popular atualmente — é a mais simples:

# Criar projeto Vite
npm create vite@latest meu-projeto -- --template vanilla

cd meu-projeto
npm install

# Adicionar Tailwind
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
// tailwind.config.js para Vite
module.exports = {
  content: ["./index.html", "./src/**/*.{js,ts}"],
  theme: { extend: {} },
  plugins: [],
}
/* src/style.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

Com essa configuração, npm run dev inicia o servidor de desenvolvimento com hot reload e recompilação automática do Tailwind.


12.4 — Classes utilitárias essenciais

Vídeo curto explicativo (link será adicionado posteriormente)

O Tailwind possui centenas de classes utilitárias. Esta seção cobre as mais utilizadas — organizadas por categoria — com a correspondência direta ao CSS que cada uma gera.

Imagem sugerida: captura da extensão Tailwind CSS IntelliSense no VS Code mostrando o autocomplete de classes com preview do CSS gerado — demonstrando como a ferramenta facilita o aprendizado das utilities.

(imagem será adicionada posteriormente)

Dica de produtividade: instale a extensão Tailwind CSS IntelliSense no VS Code. Ela fornece autocomplete, preview do CSS gerado ao passar o mouse sobre uma classe e detecção de erros. É indispensável para trabalhar com Tailwind de forma eficiente.

12.4.1 — Layout: display, position, overflow

<!-- Display -->
<div class="block">...</div>          <!-- display: block -->
<span class="inline-block">...</span> <!-- display: inline-block -->
<div class="flex">...</div>           <!-- display: flex -->
<div class="inline-flex">...</div>    <!-- display: inline-flex -->
<div class="grid">...</div>           <!-- display: grid -->
<div class="hidden">...</div>         <!-- display: none -->

<!-- Position -->
<div class="relative">...</div>  <!-- position: relative -->
<div class="absolute">...</div>  <!-- position: absolute -->
<div class="fixed">...</div>     <!-- position: fixed -->
<div class="sticky">...</div>    <!-- position: sticky -->

<!-- Coordenadas de posicionamento -->
<div class="absolute top-0 right-0">...</div>   <!-- top: 0; right: 0 -->
<div class="absolute inset-0">...</div>         <!-- top/right/bottom/left: 0 -->
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
  <!-- centralização com transform -->
</div>

<!-- Z-index -->
<div class="z-10">...</div>   <!-- z-index: 10 -->
<div class="z-50">...</div>   <!-- z-index: 50 -->
<div class="z-[1000]">...</div> <!-- valor arbitrário: z-index: 1000 -->

<!-- Overflow -->
<div class="overflow-hidden">...</div>    <!-- overflow: hidden -->
<div class="overflow-auto">...</div>      <!-- overflow: auto -->
<div class="overflow-x-auto">...</div>    <!-- overflow-x: auto -->

12.4.2 — Flexbox e Grid com Tailwind

<!-- ── Flexbox ── -->
<div class="flex flex-row gap-4 items-center justify-between">
  <!-- display: flex; flex-direction: row; gap: 1rem;
       align-items: center; justify-content: space-between -->
</div>

<!-- flex-direction -->
<div class="flex flex-col">...</div>         <!-- column -->
<div class="flex flex-row-reverse">...</div> <!-- row-reverse -->

<!-- flex-wrap -->
<div class="flex flex-wrap gap-4">...</div>  <!-- flex-wrap: wrap -->

<!-- justify-content -->
<div class="flex justify-start">...</div>
<div class="flex justify-center">...</div>
<div class="flex justify-end">...</div>
<div class="flex justify-between">...</div>
<div class="flex justify-around">...</div>
<div class="flex justify-evenly">...</div>

<!-- align-items -->
<div class="flex items-start">...</div>
<div class="flex items-center">...</div>
<div class="flex items-end">...</div>
<div class="flex items-stretch">...</div>
<div class="flex items-baseline">...</div>

<!-- flex items -->
<div class="flex-1">...</div>       <!-- flex: 1 1 0% -->
<div class="flex-auto">...</div>    <!-- flex: 1 1 auto -->
<div class="flex-none">...</div>    <!-- flex: none -->
<div class="shrink-0">...</div>     <!-- flex-shrink: 0 -->
<div class="grow">...</div>         <!-- flex-grow: 1 -->

<!-- align-self -->
<div class="self-center">...</div>
<div class="self-start">...</div>

<!-- ── Grid ── -->
<div class="grid grid-cols-3 gap-6">
  <!-- display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem -->
</div>

<!-- Colunas -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4">...</div>

<!-- Grid responsivo sem media queries -->
<div class="grid grid-cols-[repeat(auto-fit,minmax(280px,1fr))] gap-4">...</div>

<!-- Posicionamento de itens -->
<div class="col-span-2">...</div>    <!-- grid-column: span 2 -->
<div class="col-span-full">...</div> <!-- grid-column: 1 / -1 -->
<div class="row-span-2">...</div>    <!-- grid-row: span 2 -->

12.4.3 — Espaçamento: padding, margin, gap

O Tailwind usa uma escala numérica onde cada unidade equivale a 0.25rem (4px com base 16px):

<!-- Padding -->
<div class="p-4">...</div>    <!-- padding: 1rem (4 × 0.25rem) -->
<div class="px-6">...</div>   <!-- padding-left/right: 1.5rem -->
<div class="py-3">...</div>   <!-- padding-top/bottom: 0.75rem -->
<div class="pt-2 pb-4">...</div> <!-- top: 0.5rem; bottom: 1rem -->
<div class="ps-4">...</div>   <!-- padding-inline-start: 1rem (RTL-aware) -->

<!-- Margin -->
<div class="m-4">...</div>    <!-- margin: 1rem -->
<div class="mx-auto">...</div> <!-- margin-left/right: auto (centraliza) -->
<div class="mt-8 mb-4">...</div>
<div class="-mt-2">...</div>  <!-- margin-top: -0.5rem (negativo) -->

<!-- Gap (em flex e grid) -->
<div class="flex gap-4">...</div>     <!-- gap: 1rem -->
<div class="grid gap-x-6 gap-y-4">...</div> <!-- column-gap: 1.5rem; row-gap: 1rem -->

<!-- Referência da escala de espaçamento -->
<!-- 0=0, 0.5=0.125rem, 1=0.25rem, 2=0.5rem, 3=0.75rem, 4=1rem,
     5=1.25rem, 6=1.5rem, 7=1.75rem, 8=2rem, 10=2.5rem, 12=3rem,
     16=4rem, 20=5rem, 24=6rem, 32=8rem, 40=10rem, 48=12rem, 64=16rem -->

12.4.4 — Dimensionamento: width, height, max/min

<!-- Width -->
<div class="w-full">...</div>    <!-- width: 100% -->
<div class="w-1/2">...</div>     <!-- width: 50% -->
<div class="w-1/3">...</div>     <!-- width: 33.333% -->
<div class="w-64">...</div>      <!-- width: 16rem -->
<div class="w-screen">...</div>  <!-- width: 100vw -->
<div class="w-fit">...</div>     <!-- width: fit-content -->
<div class="w-auto">...</div>    <!-- width: auto -->

<!-- Height -->
<div class="h-full">...</div>    <!-- height: 100% -->
<div class="h-screen">...</div>  <!-- height: 100vh -->
<div class="h-48">...</div>      <!-- height: 12rem -->
<div class="h-fit">...</div>     <!-- height: fit-content -->
<div class="min-h-screen">...</div> <!-- min-height: 100vh -->

<!-- Max/Min width -->
<div class="max-w-sm">...</div>   <!-- max-width: 24rem -->
<div class="max-w-md">...</div>   <!-- max-width: 28rem -->
<div class="max-w-lg">...</div>   <!-- max-width: 32rem -->
<div class="max-w-xl">...</div>   <!-- max-width: 36rem -->
<div class="max-w-2xl">...</div>  <!-- max-width: 42rem -->
<div class="max-w-4xl">...</div>  <!-- max-width: 56rem -->
<div class="max-w-6xl">...</div>  <!-- max-width: 72rem -->
<div class="max-w-7xl">...</div>  <!-- max-width: 80rem -->
<div class="max-w-full">...</div> <!-- max-width: 100% -->
<div class="max-w-none">...</div> <!-- max-width: none -->

<!-- Aspect ratio -->
<div class="aspect-video">...</div>  <!-- aspect-ratio: 16 / 9 -->
<div class="aspect-square">...</div> <!-- aspect-ratio: 1 / 1 -->

12.4.5 — Tipografia

<!-- font-size -->
<p class="text-xs">...</p>    <!-- 0.75rem -->
<p class="text-sm">...</p>    <!-- 0.875rem -->
<p class="text-base">...</p>  <!-- 1rem -->
<p class="text-lg">...</p>    <!-- 1.125rem -->
<p class="text-xl">...</p>    <!-- 1.25rem -->
<p class="text-2xl">...</p>   <!-- 1.5rem -->
<p class="text-3xl">...</p>   <!-- 1.875rem -->
<p class="text-4xl">...</p>   <!-- 2.25rem -->
<p class="text-5xl">...</p>   <!-- 3rem -->
<p class="text-6xl">...</p>   <!-- 3.75rem -->

<!-- font-weight -->
<p class="font-thin">...</p>       <!-- 100 -->
<p class="font-light">...</p>      <!-- 300 -->
<p class="font-normal">...</p>     <!-- 400 -->
<p class="font-medium">...</p>     <!-- 500 -->
<p class="font-semibold">...</p>   <!-- 600 -->
<p class="font-bold">...</p>       <!-- 700 -->
<p class="font-extrabold">...</p>  <!-- 800 -->
<p class="font-black">...</p>      <!-- 900 -->

<!-- line-height -->
<p class="leading-none">...</p>    <!-- 1 -->
<p class="leading-tight">...</p>   <!-- 1.25 -->
<p class="leading-snug">...</p>    <!-- 1.375 -->
<p class="leading-normal">...</p>  <!-- 1.5 -->
<p class="leading-relaxed">...</p> <!-- 1.625 -->
<p class="leading-loose">...</p>   <!-- 2 -->

<!-- text-align -->
<p class="text-left">...</p>
<p class="text-center">...</p>
<p class="text-right">...</p>
<p class="text-justify">...</p>

<!-- letter-spacing -->
<p class="tracking-tight">...</p>   <!-- -0.05em -->
<p class="tracking-normal">...</p>  <!-- 0 -->
<p class="tracking-wide">...</p>    <!-- 0.025em -->
<p class="tracking-wider">...</p>   <!-- 0.05em -->
<p class="tracking-widest">...</p>  <!-- 0.1em -->

<!-- text-decoration -->
<a class="underline">...</a>
<a class="no-underline">...</a>
<p class="line-through">...</p>

<!-- text-transform -->
<p class="uppercase">...</p>
<p class="lowercase">...</p>
<p class="capitalize">...</p>

<!-- Truncamento *)
<p class="truncate">Texto longo que será truncado...</p>
<!-- overflow: hidden; text-overflow: ellipsis; white-space: nowrap -->

12.4.6 — Cores: text, background, border

O Tailwind inclui uma paleta de cores extensa com escalas de 50 a 950 para cada tom. A nomenclatura segue o padrão {propriedade}-{cor}-{escala}:

<!-- text color -->
<p class="text-slate-900">...</p>   <!-- cor escura para texto -->
<p class="text-slate-600">...</p>   <!-- cinza médio secundário -->
<p class="text-blue-700">...</p>    <!-- azul para links/destaques -->
<p class="text-white">...</p>       <!-- branco -->

<!-- background color -->
<div class="bg-white">...</div>
<div class="bg-slate-50">...</div>   <!-- fundo levemente cinza -->
<div class="bg-slate-900">...</div>  <!-- fundo escuro -->
<div class="bg-blue-700">...</div>   <!-- fundo azul (primário) -->
<div class="bg-blue-50">...</div>    <!-- azul muito claro (destaque sutil) -->

<!-- border color -->
<div class="border border-slate-200">...</div>  <!-- borda cinza clara -->
<div class="border border-blue-500">...</div>   <!-- borda azul -->

<!-- Cores com opacidade -->
<div class="bg-blue-700/80">...</div>      <!-- bg com 80% de opacidade -->
<p class="text-slate-900/70">...</p>       <!-- texto com 70% de opacidade -->
<div class="border border-black/10">...</div>  <!-- borda preta 10% opacidade -->

Paleta de cores do Tailwind (tons principais):

Cor Uso típico
slate Texto, fundos neutros, bordas
gray Alternativa neutra ao slate
zinc Neutro com tom levemente quente
red Erros, perigo, alertas críticos
orange Avisos, destaques
yellow Avisos suaves
green Sucesso, confirmação
blue Ação primária, links, informação
indigo Alternativa ao azul
purple Destaques visuais
pink Uso decorativo

12.4.7 — Bordas: border, border-radius, outline

<!-- border -->
<div class="border">...</div>           <!-- border: 1px solid -->
<div class="border-2">...</div>         <!-- border-width: 2px -->
<div class="border-4">...</div>         <!-- border-width: 4px -->
<div class="border-t">...</div>         <!-- border-top apenas -->
<div class="border-b border-slate-200">...</div> <!-- bottom com cor -->
<div class="border-0">...</div>         <!-- remove borda -->

<!-- border-radius -->
<div class="rounded-none">...</div>    <!-- 0 -->
<div class="rounded-sm">...</div>      <!-- 0.125rem -->
<div class="rounded">...</div>         <!-- 0.25rem -->
<div class="rounded-md">...</div>      <!-- 0.375rem -->
<div class="rounded-lg">...</div>      <!-- 0.5rem -->
<div class="rounded-xl">...</div>      <!-- 0.75rem -->
<div class="rounded-2xl">...</div>     <!-- 1rem -->
<div class="rounded-full">...</div>    <!-- 9999px — círculo/pílula -->

<!-- outline (foco) -->
<button class="focus-visible:outline focus-visible:outline-2
               focus-visible:outline-offset-2 focus-visible:outline-blue-600">
  Botão acessível
</button>

<!-- ring (foco — alternativa com box-shadow) *)
<input class="focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
              focus:outline-none" />

12.4.8 — Sombras e opacidade

<!-- box-shadow -->
<div class="shadow-sm">...</div>   <!-- sombra sutil *)
<div class="shadow">...</div>      <!-- sombra padrão *)
<div class="shadow-md">...</div>   <!-- sombra média *)
<div class="shadow-lg">...</div>   <!-- sombra grande *)
<div class="shadow-xl">...</div>   <!-- sombra extra grande *)
<div class="shadow-2xl">...</div>  <!-- sombra máxima *)
<div class="shadow-none">...</div> <!-- remove sombra *)

<!-- opacity *)
<div class="opacity-0">...</div>    <!-- opacity: 0 (invisível) *)
<div class="opacity-50">...</div>   <!-- opacity: 0.5 *)
<div class="opacity-75">...</div>   <!-- opacity: 0.75 *)
<div class="opacity-100">...</div>  <!-- opacity: 1 (opaco) *)

12.4.9 — Transições e animações básicas

<!-- transition *)
<button class="transition-colors duration-200">...</button>
<!-- transition: color, background-color, border-color... 200ms *)

<div class="transition-all duration-300 ease-in-out">...</div>
<!-- transition: all 300ms ease-in-out *)

<div class="transition-transform duration-150">...</div>

<!-- duration -->
<div class="duration-75">...</div>   <!-- 75ms *)
<div class="duration-100">...</div>  <!-- 100ms *)
<div class="duration-150">...</div>  <!-- 150ms *)
<div class="duration-200">...</div>  <!-- 200ms *)
<div class="duration-300">...</div>  <!-- 300ms *)
<div class="duration-500">...</div>  <!-- 500ms *)
<div class="duration-700">...</div>  <!-- 700ms *)

<!-- easing *)
<div class="ease-linear">...</div>
<div class="ease-in">...</div>
<div class="ease-out">...</div>
<div class="ease-in-out">...</div>

<!-- transform *)
<div class="hover:scale-105">...</div>         <!-- scale(1.05) *)
<div class="hover:-translate-y-1">...</div>    <!-- translateY(-0.25rem) *)
<div class="hover:rotate-3">...</div>          <!-- rotate(3deg) *)

<!-- animate (animações predefinidas) *)
<div class="animate-spin">...</div>    <!-- rotação contínua *)
<div class="animate-ping">...</div>    <!-- pulso expansivo *)
<div class="animate-pulse">...</div>   <!-- pulso de opacidade *)
<div class="animate-bounce">...</div>  <!-- salto *)

12.5 — Responsividade com Tailwind

Vídeo curto explicativo (link será adicionado posteriormente)

12.5.1 — O sistema de breakpoints do Tailwind

O Tailwind implementa um sistema de breakpoints baseado em min-width — mobile-first por padrão:

Prefixo Breakpoint CSS gerado
(nenhum) < 640px estilos base (mobile)
sm: ≥ 640px @media (min-width: 640px)
md: ≥ 768px @media (min-width: 768px)
lg: ≥ 1024px @media (min-width: 1024px)
xl: ≥ 1280px @media (min-width: 1280px)
2xl: ≥ 1536px @media (min-width: 1536px)

12.5.2 — Prefixos responsivos

Qualquer utility pode ser prefixada com um breakpoint para condicionar sua aplicação:

<!-- Sem prefixo: aplica em todos os tamanhos (mobile-first) -->
<!-- Com prefixo: aplica apenas a partir daquele breakpoint -->

<div class="text-base md:text-lg lg:text-xl">
  Texto que cresce com o viewport
</div>

<div class="flex flex-col md:flex-row gap-4 md:gap-6">
  Empilhado em mobile, lado a lado a partir de md
</div>

<div class="hidden md:block">
  Oculto em mobile, visível a partir de md
</div>

<div class="block md:hidden">
  Visível apenas em mobile
</div>

<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
  Grade que aumenta colunas progressivamente
</div>

12.5.3 — Mobile-first por padrão

O sistema de breakpoints do Tailwind reforça a abordagem mobile-first apresentada no Capítulo 10: os estilos sem prefixo são os estilos base (mobile), e os prefixos adicionam sobrescrições progressivas para viewports maiores:

<!-- Mobile-first com Tailwind -->
<nav class="
  flex flex-col gap-2 p-4
  md:flex-row md:items-center md:justify-between md:px-8 md:py-4
  lg:px-12
">
  <!-- Mobile: coluna, padding pequeno -->
  <!-- md: linha, centralizado, padding médio -->
  <!-- lg: padding maior -->
</nav>

12.5.4 — Exemplos práticos de layout responsivo

Hero section responsiva:

<section class="
  flex flex-col gap-8 px-4 py-12
  md:flex-row md:items-center md:gap-12 md:px-8 md:py-20
  lg:px-16 lg:py-28 lg:gap-16
  max-w-7xl mx-auto
">
  <div class="flex-1 space-y-6">
    <h1 class="text-3xl font-bold leading-tight md:text-4xl lg:text-5xl">
      Título principal da seção
    </h1>
    <p class="text-lg text-slate-600 leading-relaxed max-w-prose">
      Descrição da seção...
    </p>
    <div class="flex flex-col sm:flex-row gap-3">
      <a href="#" class="px-6 py-3 bg-blue-700 text-white font-semibold
                         rounded-lg text-center hover:bg-blue-800 transition-colors">
        Ação primária
      </a>
      <a href="#" class="px-6 py-3 border-2 border-blue-700 text-blue-700
                         font-semibold rounded-lg text-center
                         hover:bg-blue-50 transition-colors">
        Ação secundária
      </a>
    </div>
  </div>
  <div class="flex-1">
    <img src="hero.jpg" alt="Imagem hero"
         class="w-full rounded-2xl shadow-xl" />
  </div>
</section>

12.6 — Estados e variantes

Vídeo curto explicativo (link será adicionado posteriormente)

12.6.1 — Pseudo-classes: hover:, focus:, active:, disabled:

<!-- hover -->
<button class="bg-blue-700 hover:bg-blue-800 text-white px-4 py-2 rounded-lg
               transition-colors duration-200">
  Botão com hover
</button>

<!-- focus e focus-visible -->
<input class="border border-slate-300 rounded-md px-3 py-2
              focus:outline-none focus:border-blue-500
              focus:ring-2 focus:ring-blue-500/20" />

<button class="focus-visible:outline focus-visible:outline-2
               focus-visible:outline-offset-2 focus-visible:outline-blue-600">
  Foco visível apenas via teclado
</button>

<!-- active -->
<button class="active:scale-95 active:bg-blue-900 transition-transform">
  Pressionar reduz levemente
</button>

<!-- disabled -->
<button class="disabled:opacity-50 disabled:cursor-not-allowed
               disabled:pointer-events-none" disabled>
  Desabilitado
</button>

<!-- group: aplica estilos a filhos quando o pai recebe hover *)
<div class="group flex items-center gap-3 p-4 rounded-lg
            hover:bg-slate-100 cursor-pointer">
  <div class="w-10 h-10 rounded-full bg-blue-100 group-hover:bg-blue-200
              transition-colors">
  </div>
  <p class="font-medium group-hover:text-blue-700 transition-colors">
    Texto que muda quando o card recebe hover
  </p>
</div>

12.6.2 — Pseudo-elementos: before:, after:, placeholder:

<!-- placeholder -->
<input class="placeholder:text-slate-400 placeholder:italic
              border rounded-md px-3 py-2"
       placeholder="Digite aqui..." />

<!-- before e after *)
<div class="relative before:absolute before:inset-0
            before:bg-black/40 before:rounded-lg">
  <img src="foto.jpg" alt="Com overlay" class="rounded-lg" />
</div>

<!-- first: e last: — primeiro e último filho *)
<ul>
  <li class="py-3 border-b border-slate-100 first:pt-0 last:border-0">
    Item da lista
  </li>
</ul>

<!-- odd: e even: — alternância de linhas *)
<tr class="odd:bg-white even:bg-slate-50">
  <td class="px-4 py-3">...</td>
</tr>

12.6.3 — Estados de formulário

<!-- required, invalid, valid *)
<input class="border rounded-md px-3 py-2
              required:border-slate-300
              invalid:border-red-500 invalid:ring-2 invalid:ring-red-500/20
              valid:border-green-500"
       type="email" required />

<!-- checked (checkbox/radio) *)
<input class="accent-blue-700 w-4 h-4 cursor-pointer"
       type="checkbox" />

<!-- peer: aplica estilo a irmão com base no estado do elemento *)
<div class="flex flex-col gap-1">
  <input class="peer border rounded-md px-3 py-2
                focus:outline-none focus:border-blue-500"
         type="email" required />
  <p class="hidden peer-invalid:block text-sm text-red-600">
    E-mail inválido
  </p>
</div>

12.6.4 — Dark mode: dark:

<!-- dark: aplica estilos quando o tema escuro está ativo *)
<div class="bg-white dark:bg-slate-900
            text-slate-900 dark:text-slate-100
            border border-slate-200 dark:border-slate-700
            p-6 rounded-xl">
  <h2 class="text-xl font-bold text-slate-900 dark:text-white">
    Título adaptativo
  </h2>
  <p class="text-slate-600 dark:text-slate-400 mt-2">
    Conteúdo que se adapta ao tema do sistema.
  </p>
</div>

Por padrão, o modo escuro do Tailwind usa prefers-color-scheme: dark. Para controle manual via classe .dark no <html>, configure darkMode: 'class' no tailwind.config.js.

12.6.5 — Combinando variantes

Variantes podem ser combinadas livremente — a ordem de escrita segue a lógica {breakpoint}:{estado}:{utility}:

<!-- Responsivo + estado *)
<button class="
  bg-blue-700 text-white
  hover:bg-blue-800
  md:text-lg md:px-8 md:py-4
  md:hover:bg-blue-900
  dark:bg-blue-600 dark:hover:bg-blue-500
  disabled:opacity-50 disabled:cursor-not-allowed
  transition-all duration-200
  focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-300
">
  Botão com múltiplas variantes
</button>

12.7 — Componentes com Tailwind

Vídeo curto explicativo (link será adicionado posteriormente)

12.7.1 — Construindo um botão com variantes

<!-- Botão primário *)
<button class="inline-flex items-center justify-center gap-2
               px-4 py-2 rounded-lg font-semibold text-sm
               bg-blue-700 text-white
               hover:bg-blue-800 active:bg-blue-900
               focus-visible:outline focus-visible:outline-2
               focus-visible:outline-offset-2 focus-visible:outline-blue-600
               disabled:opacity-50 disabled:cursor-not-allowed
               transition-colors duration-200"
        type="button">
  Salvar
</button>

<!-- Botão secundário (outline) *)
<button class="inline-flex items-center justify-center gap-2
               px-4 py-2 rounded-lg font-semibold text-sm
               border-2 border-blue-700 text-blue-700 bg-transparent
               hover:bg-blue-50 active:bg-blue-100
               focus-visible:outline focus-visible:outline-2
               focus-visible:outline-offset-2 focus-visible:outline-blue-600
               disabled:opacity-50 disabled:cursor-not-allowed
               transition-colors duration-200"
        type="button">
  Cancelar
</button>

<!-- Botão de perigo *)
<button class="inline-flex items-center justify-center gap-2
               px-4 py-2 rounded-lg font-semibold text-sm
               bg-red-600 text-white
               hover:bg-red-700 active:bg-red-800
               focus-visible:outline focus-visible:outline-2
               focus-visible:outline-offset-2 focus-visible:outline-red-600
               disabled:opacity-50 disabled:cursor-not-allowed
               transition-colors duration-200"
        type="button">
  Excluir
</button>

12.7.2 — Card responsivo

<article class="bg-white rounded-xl shadow-md overflow-hidden
                flex flex-col
                hover:shadow-lg hover:-translate-y-1 transition-all duration-300
                focus-within:outline focus-within:outline-2
                focus-within:outline-blue-500">
  <img src="imagem.jpg" alt="Descrição"
       class="w-full h-48 object-cover" />

  <div class="flex-1 p-6 flex flex-col gap-3">
    <div class="flex items-center gap-2">
      <span class="text-xs font-semibold text-blue-700 bg-blue-50
                   px-2.5 py-0.5 rounded-full uppercase tracking-wide">
        Categoria
      </span>
    </div>

    <h2 class="text-xl font-semibold text-slate-900 leading-snug">
      Título do card
    </h2>

    <p class="text-slate-600 leading-relaxed text-sm flex-1">
      Descrição do conteúdo do card que pode ser mais ou menos longa.
    </p>
  </div>

  <div class="px-6 pb-6 flex items-center justify-between">
    <span class="text-xs text-slate-400">15 mar. 2026</span>
    <a href="#"
       class="text-sm font-semibold text-blue-700 hover:text-blue-800
              hover:underline focus-visible:outline focus-visible:outline-2
              focus-visible:outline-offset-2 focus-visible:outline-blue-600
              rounded">
      Leia mais →
    </a>
  </div>
</article>

12.7.3 — Navbar responsiva

<header class="bg-slate-900 text-white shadow-lg sticky top-0 z-50">
  <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
    <div class="flex items-center justify-between h-16">

      <!-- Logo *)
      <a href="/" class="flex items-center gap-2 font-bold text-lg
                         hover:text-blue-400 transition-colors">
        <img src="logo.svg" alt="IFAL" class="h-8 w-auto" />
        <span>PWEB1</span>
      </a>

      <!-- Links desktop: ocultos em mobile *)
      <nav class="hidden md:flex items-center gap-1"
           aria-label="Navegação principal">
        <a href="/"
           class="px-3 py-2 rounded-md text-sm font-medium
                  text-white hover:bg-slate-700 transition-colors
                  aria-[current=page]:bg-slate-700"
           aria-current="page">
          Início
        </a>
        <a href="/capitulos"
           class="px-3 py-2 rounded-md text-sm font-medium
                  text-slate-300 hover:text-white hover:bg-slate-700
                  transition-colors">
          Capítulos
        </a>
        <a href="/atividades"
           class="px-3 py-2 rounded-md text-sm font-medium
                  text-slate-300 hover:text-white hover:bg-slate-700
                  transition-colors">
          Atividades
        </a>
      </nav>

      <!-- Botão menu mobile *)
      <button class="md:hidden p-2 rounded-md text-slate-400
                     hover:text-white hover:bg-slate-700 transition-colors
                     focus-visible:outline focus-visible:outline-2
                     focus-visible:outline-white"
              aria-controls="menu-mobile"
              aria-expanded="false"
              aria-label="Abrir menu"
              type="button">
        <svg class="w-6 h-6" fill="none" stroke="currentColor"
             viewBox="0 0 24 24" aria-hidden="true">
          <path stroke-linecap="round" stroke-linejoin="round"
                stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
        </svg>
      </button>

    </div>
  </div>

  <!-- Menu mobile (toggle via JS) *)
  <div class="md:hidden hidden" id="menu-mobile">
    <nav class="px-2 pt-2 pb-3 space-y-1 border-t border-slate-700"
         aria-label="Menu mobile">
      <a href="/"
         class="block px-3 py-2 rounded-md text-base font-medium
                bg-slate-700 text-white">
        Início
      </a>
      <a href="/capitulos"
         class="block px-3 py-2 rounded-md text-base font-medium
                text-slate-300 hover:text-white hover:bg-slate-700
                transition-colors">
        Capítulos
      </a>
    </nav>
  </div>
</header>

12.7.4 — Formulário de contato estilizado

<form class="max-w-lg mx-auto bg-white rounded-2xl shadow-lg p-8 space-y-6"
      action="/contato" method="post">

  <div class="space-y-1">
    <label class="block text-sm font-medium text-slate-700" for="nome">
      Nome completo <span class="text-red-500" aria-hidden="true">*</span>
    </label>
    <input class="w-full px-3 py-2 border border-slate-300 rounded-lg
                  text-slate-900 placeholder:text-slate-400
                  focus:outline-none focus:border-blue-500
                  focus:ring-2 focus:ring-blue-500/20
                  transition-shadow duration-200"
           type="text" id="nome" name="nome" required
           placeholder="Maria Silva" />
  </div>

  <div class="space-y-1">
    <label class="block text-sm font-medium text-slate-700" for="email">
      E-mail <span class="text-red-500" aria-hidden="true">*</span>
    </label>
    <input class="w-full px-3 py-2 border border-slate-300 rounded-lg
                  text-slate-900 placeholder:text-slate-400
                  focus:outline-none focus:border-blue-500
                  focus:ring-2 focus:ring-blue-500/20
                  transition-shadow duration-200"
           type="email" id="email" name="email" required
           placeholder="maria@exemplo.com" />
  </div>

  <div class="space-y-1">
    <label class="block text-sm font-medium text-slate-700" for="mensagem">
      Mensagem <span class="text-red-500" aria-hidden="true">*</span>
    </label>
    <textarea class="w-full px-3 py-2 border border-slate-300 rounded-lg
                     text-slate-900 placeholder:text-slate-400
                     focus:outline-none focus:border-blue-500
                     focus:ring-2 focus:ring-blue-500/20
                     transition-shadow duration-200 resize-y min-h-[120px]"
              id="mensagem" name="mensagem" rows="5" required
              placeholder="Sua mensagem..."></textarea>
  </div>

  <button class="w-full px-6 py-3 bg-blue-700 text-white font-semibold
                 rounded-lg hover:bg-blue-800 active:bg-blue-900
                 focus-visible:outline focus-visible:outline-2
                 focus-visible:outline-offset-2 focus-visible:outline-blue-600
                 disabled:opacity-50 disabled:cursor-not-allowed
                 transition-colors duration-200"
          type="submit">
    Enviar mensagem
  </button>

</form>

12.7.5 — A diretiva @apply: extraindo componentes reutilizáveis

Quando um mesmo conjunto de classes é repetido em muitos lugares, a diretiva @apply permite extrair essas classes para uma classe CSS reutilizável — mantendo os benefícios do utility-first sem duplicação:

/* src/input.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  /* Componente btn extraído com @apply *)
  .btn {
    @apply inline-flex items-center justify-center gap-2
           px-4 py-2 rounded-lg font-semibold text-sm
           transition-colors duration-200 cursor-pointer
           focus-visible:outline focus-visible:outline-2
           focus-visible:outline-offset-2
           disabled:opacity-50 disabled:cursor-not-allowed;
  }

  .btn-primario {
    @apply bg-blue-700 text-white
           hover:bg-blue-800 active:bg-blue-900
           focus-visible:outline-blue-600;
  }

  .btn-secundario {
    @apply border-2 border-blue-700 text-blue-700 bg-transparent
           hover:bg-blue-50 active:bg-blue-100
           focus-visible:outline-blue-600;
  }

  .btn-perigo {
    @apply bg-red-600 text-white
           hover:bg-red-700 active:bg-red-800
           focus-visible:outline-red-600;
  }

  /* Componente campo extraído *)
  .campo {
    @apply flex flex-col gap-1;
  }

  .campo-label {
    @apply block text-sm font-medium text-slate-700;
  }

  .campo-input {
    @apply w-full px-3 py-2 border border-slate-300 rounded-lg
           text-slate-900 placeholder:text-slate-400
           focus:outline-none focus:border-blue-500
           focus:ring-2 focus:ring-blue-500/20
           transition-shadow duration-200;
  }
}

Uso em HTML após extração:

<!-- Antes: classes repetidas em cada botão *)
<button class="inline-flex items-center px-4 py-2 rounded-lg font-semibold
               text-sm bg-blue-700 text-white hover:bg-blue-800 ...">
  Salvar
</button>

<!-- Depois: classes extraídas e reutilizáveis *)
<button class="btn btn-primario" type="button">Salvar</button>
<button class="btn btn-secundario" type="button">Cancelar</button>
<button class="btn btn-perigo" type="button">Excluir</button>

Quando usar @apply: reserve @apply para componentes genuinamente reutilizados em múltiplos lugares — botões, campos de formulário, badges. Para elementos únicos, mantenha as classes diretamente no HTML. O uso excessivo de @apply reconstrói o CSS semântico que o utility-first foi criado para evitar.


12.8 — Customização do Tailwind

Vídeo curto explicativo (link será adicionado posteriormente)

12.8.1 — Estendendo o tema: cores, fontes e espaçamentos personalizados

theme.extend adiciona valores ao tema padrão sem substituí-lo:

// tailwind.config.js
module.exports = {
  content: ['./src/**/*.{html,js}'],
  theme: {
    extend: {
      // Cores customizadas — adicionadas à paleta existente
      colors: {
        'ifal': {
          50:  '#eff6ff',
          100: '#dbeafe',
          500: '#3b82f6',
          700: '#1d4ed8',
          900: '#1e3a8a',
        },
        'destaque': '#E8632A',
      },

      // Fontes customizadas
      fontFamily: {
        'sans': ['Inter', 'system-ui', 'sans-serif'],
        'serif': ['Merriweather', 'Georgia', 'serif'],
        'mono':  ['Fira Code', 'Consolas', 'monospace'],
      },

      // Espaçamentos adicionais à escala existente
      spacing: {
        '18': '4.5rem',
        '22': '5.5rem',
        '88': '22rem',
        '112': '28rem',
        '128': '32rem',
      },

      // Border radius customizado
      borderRadius: {
        '4xl': '2rem',
        '5xl': '2.5rem',
      },

      // Breakpoints adicionais
      screens: {
        'xs': '475px',
        '3xl': '1920px',
      },

      // max-width customizado
      maxWidth: {
        '8xl': '88rem',
        '9xl': '96rem',
      },
    },
  },
}

12.8.2 — Sobrescrevendo valores padrão

theme.XXX (sem extend) substitui completamente os valores padrão daquela categoria:

module.exports = {
  theme: {
    // SUBSTITUI todos os breakpoints — use com cuidado
    screens: {
      'mobile': '375px',
      'tablet': '768px',
      'desktop': '1024px',
      'wide': '1440px',
    },

    // SUBSTITUI todas as cores — o projeto não terá blue, red, etc.
    // Use extend para ADICIONAR; use theme para SUBSTITUIR
    colors: {
      transparent: 'transparent',
      current: 'currentColor',
      white: '#ffffff',
      black: '#000000',
      primaria: { /* escala completa */ },
    },

    extend: {
      // Adições aqui não afetam as substituições acima
    },
  },
}

12.8.3 — Criando utilitários customizados com @layer

Para propriedades CSS não cobertas pelo Tailwind, @layer utilities adiciona utilities customizadas que se comportam como as nativas — incluindo suporte a variantes responsivas e de estado:

@layer utilities {
  /* Utility customizada: scrollbar oculta *)
  .scrollbar-oculto {
    -ms-overflow-style: none;
    scrollbar-width: none;
  }

  .scrollbar-oculto::-webkit-scrollbar {
    display: none;
  }

  /* Texto com gradiente *)
  .texto-gradiente {
    background-image: linear-gradient(135deg, #1d4ed8, #E8632A);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
  }

  /* Fundo com padrão *)
  .bg-grade {
    background-image:
      linear-gradient(rgba(0,0,0,0.05) 1px, transparent 1px),
      linear-gradient(90deg, rgba(0,0,0,0.05) 1px, transparent 1px);
    background-size: 20px 20px;
  }
}

12.8.4 — Relação entre tokens do Design System e o tema Tailwind

O Capítulo 11 construiu um Design System baseado em variáveis CSS com hierarquia de tokens. O Tailwind pode ser configurado para consumir esses tokens — mantendo consistência entre o sistema de design e o framework:

// tailwind.config.js — consumindo tokens CSS como variáveis
module.exports = {
  theme: {
    extend: {
      colors: {
        // Referencia as variáveis CSS dos tokens semânticos
        'primaria':   'var(--cor-primaria)',
        'secundaria': 'var(--cor-secundaria)',
        'destaque':   'var(--cor-destaque)',
        'fundo':      'var(--cor-fundo-pagina)',
        'texto':      'var(--cor-texto-padrao)',
        'texto-2':    'var(--cor-texto-secundario)',
        'borda':      'var(--cor-borda-padrao)',
        'sucesso':    'var(--cor-sucesso)',
        'erro':       'var(--cor-erro)',
        'aviso':      'var(--cor-aviso)',
      },

      fontFamily: {
        'sem-serifa': 'var(--fonte-sem-serifa)',
        'serifa':     'var(--fonte-serifa)',
        'codigo':     'var(--fonte-codigo)',
      },

      borderRadius: {
        'sm':   'var(--raio-sm)',
        'md':   'var(--raio-md)',
        'lg':   'var(--raio-lg)',
        'xl':   'var(--raio-xl)',
        'full': 'var(--raio-circulo)',
      },
    },
  },
}
<!-- Usando tokens do Design System via Tailwind *)
<button class="bg-primaria text-white font-semibold px-4 py-2
               rounded-md hover:bg-blue-800 transition-colors">
  Usa var(--cor-primaria)
</button>

<!-- Tema escuro funciona automaticamente porque --cor-primaria
     é redefinida na media query prefers-color-scheme: dark *)

Esta integração fecha o arco entre os Capítulos 11 e 12: o Design System define os tokens e as decisões de design; o Tailwind consome esses tokens como classes utilitárias. Mudanças nos tokens se propagam tanto para o CSS customizado quanto para o Tailwind — mantendo consistência em todo o projeto.

Referências: - Tailwind CSS — Documentação oficial - Tailwind CSS — Customizing the theme - Tailwind CSS — Using CSS variables - Adam Wathan — CSS Utility Classes and "Separation of Concerns" - State of CSS 2024


Atividades — Capítulo 12

1. Qual é a diferença fundamental entre um framework utility-first como Tailwind e um framework component-based como Bootstrap?

2. Um elemento tem as classes text-base md:text-lg lg:text-2xl. Em um viewport de 900px, qual tamanho de fonte é aplicado?

3. Quando é apropriado usar a diretiva @apply no Tailwind?

  • GitHub Classroom: Reestilizar a landing page do 2º Bimestre usando Tailwind CSS: instalar via npm, configurar o tema com as cores e fontes do Design System do Capítulo 11, implementar a navbar responsiva com menu mobile, a seção de cards com hover states e o formulário de contato — tudo sem CSS customizado, usando apenas Tailwind. (link será adicionado)

:material-arrow-left: Voltar ao Capítulo 11 — Variáveis CSS e Design System :material-arrow-right: Ir ao Capítulo 13 — JavaScript Essencial