Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
ComparaçãoIntermediáriocódigo testado

Tailwind vs CSS puro e CSS Modules: qual gera menos CSS

A mesma página portada três vezes e compilada: bytes de CSS, número de regras e tempo de build na mesma máquina, mais a tabela de quando usar cada um.

Rodolfo Mori10 min de leitura

No experimento pequeno desta comparação, CSS puro gerou menos CSS: 657 bytes contra 720 de CSS Modules e 12.102 de Tailwind completo. Isso não encerra a discussão. O caso tem um cartão e, por isso, o piso de tema e Preflight domina o Tailwind; a curva pode mudar quando dezenas de telas reutilizam as mesmas utilities.

A resposta útil não é “qual framework vence”, mas qual custo você está medindo: artefato, escopo, remoção, velocidade de alteração, consistência ou integração.

O mesmo cartão e a fronteira do teste

As três versões renderizam um cartão centralizado da clínica, com título, parágrafo e botão. Mesmos valores de cor, tamanho, padding, raio e foco. Não é uma landing page de 14 seções porque eu não poderia validar visualmente essa extensão neste ambiente; reduzi o ângulo a um componente controlado e declarei a limitação.

Tailwind:

html
<main class="grid min-h-screen place-items-center bg-slate-50 p-8">
  <article class="flex w-full max-w-lg flex-col gap-4 rounded-xl bg-white p-6 shadow-lg">
    <h2 class="text-3xl font-bold text-slate-900">Agenda</h2>
    <p class="leading-7 text-slate-600">Três consultas confirmadas.</p>
    <button class="rounded-lg bg-cyan-600 px-4 py-2 font-bold text-white hover:bg-cyan-700">
      Abrir agenda
    </button>
  </article>
</main>

CSS puro:

css
.painel { display:grid; min-height:100vh; place-items:center; padding:2rem; background:#f8fafc; }
.cartao { display:flex; flex-direction:column; gap:1rem; width:min(100%,32rem); padding:1.5rem; }
.acao { border:0; border-radius:.5rem; padding:.5rem 1rem; background:#0891b2; color:#fff; }
.acao:hover { background:#0e7490; }

CSS Modules usa as mesmas declarações, importadas como objeto de classes:

js
import s from './styles.module.css';

document.querySelector('#app').innerHTML =
  `<main class="${s.painel}"><article class="${s.cartao}">...</article></main>`;

Metodologia reproduzível

Usei Node 26.3.0. Tailwind 4.3.3 compilou CSS com fonte explícita. Vite 8.2.2 construiu as três variantes para produção. Depois um script Node leu o único asset CSS de cada pasta e calculou bytes crus, gzip nível 9, Brotli e quantidade de blocos por chaves.

bash
npx @tailwindcss/cli -i styles.css -o compiled.css --minify
npx vite build bench-tailwind
npx vite build bench-puro
npx vite build bench-modulos
node measure.mjs

As fontes e saídas ficaram no mesmo diretório temporário, no mesmo volume e na mesma execução. Não houve download de fonte, imagem ou CSS externo.

js
const bruto = readFileSync(arquivoCss);
console.log(bruto.length);
console.log(gzipSync(bruto, { level: 9 }).length);
console.log(brotliCompressSync(bruto).length);

Contagem de “blocos” inclui regras e at-rules com chaves; não equivale a número de seletores simples. O método serve para comparar os três artefatos medidos, não para afirmar complexidade cognitiva.

Bytes cru, gzip e Brotli

Os resultados foram:

variante CSS cru gzip -9 Brotli blocos contados
Tailwind completo 12.102 B 3.489 B 3.041 B 166
CSS puro 657 B 379 B 290 B 9
CSS Modules 720 B 391 B 306 B 9
bench-tailwind: 12102 bytes; gzip 3489; brotli 3041; 166 blocos bench-puro: 657 bytes; gzip 379; brotli 290; 9 blocos bench-modulos: 720 bytes; gzip 391; brotli 306; 9 blocos

Tailwind inclui Preflight, tokens de tema e propriedades auxiliares. Para medir utilities sem Preflight, compilei theme e utilities; ainda foram 8.374 bytes crus e 2.537 gzip, porque tokens usados e infraestrutura continuam presentes.

css
@import "tailwindcss/theme";
@import "tailwindcss/utilities";
@source "./main.js";

Remover Preflight muda a base visual e não é otimização gratuita. Você precisa substituir normalização e verificar controles, mídia e tipografia.

Por que o cartão favorece CSS manual

No CSS puro, cada declaração aparece uma vez e atende exatamente o exemplo. Não há paleta, escala tipográfica ou propriedades preparadas para outros estados. Esse minimalismo é vantagem real num artefato pequeno e estável.

Tailwind paga um piso para oferecer uma API coerente. O mesmo p-6 usado em cem cartões continua uma regra CSS. Contudo, o HTML repete a string, e o documento também trafega bytes. Compressão reduz repetição, mas não zera. Uma comparação de aplicação precisa somar HTML ou JavaScript, CSS e cache, não apenas a folha.

CSS Modules adicionou 63 bytes sobre o CSS puro neste teste, principalmente por transformação de escopo. Seu benefício é evitar colisão entre .cartao de dois componentes, não gerar o menor arquivo possível.

Quando a página dobra

Duplicar o mesmo componente sem introduzir classes novas não aumenta as regras Tailwind; também não aumenta CSS puro se o seletor é reutilizado. A diferença surge quando a segunda página pede variações.

html
<article class="rounded-xl bg-white p-6">...</article>
<article class="rounded-xl bg-slate-900 p-8 text-white">...</article>

Tailwind adiciona somente utilities ainda ausentes. CSS puro adiciona ou amplia seletores conforme a arquitetura. Um sistema bem desenhado pode reutilizar tokens e modificadores; um sistema apressado duplica blocos. Ferramenta não determina disciplina.

Para medir curva, gere versões com 1, 10 e 100 componentes representativos, mantendo conteúdo e estados. Separe crescimento do HTML e do CSS. O número desta página não responde essa pergunta; ele estabelece um baseline honesto.

bash
for quantidade in 1 10 100; do
  gerar-casos "$quantidade"
  medir-assets "$quantidade"
done

Esse pseudocomando descreve a próxima medição; não há saída alegada para ele. Num experimento real, versione o gerador para que as três implementações recebam o mesmo conteúdo e as mesmas variações.

Tempo de build foi observado, não tratado como benchmark

As saídas individuais mostraram 45ms no CLI e builds Vite de 68ms, 32ms e 53ms em uma execução. Não apresento isso como ranking. Uma amostra única sofre com cache, aquecimento de processo e agendamento do sistema.

Tailwind CLI: Done in 45ms Vite Tailwind: built in 68ms Vite CSS puro: built in 32ms Vite CSS Modules: built in 53ms

Para comparar tempo de verdade, rode dezenas de iterações, descarte aquecimento, registre mediana e percentis e teste rebuild em watch. O título desta página promete bytes e regras; tempo aparece como limite metodológico, não como conclusão inflada.

O que números de bundle não medem

Eles não medem quanto tempo uma pessoa leva para localizar estilo, remover uma tela, revisar um token ou impedir uma cor fora do sistema. Também não medem qualidade semântica e acessibilidade. É possível produzir um bundle mínimo com foco invisível e contraste ruim.

Tailwind aproxima estilo do componente e oferece escala compartilhada. CSS puro permite qualquer recurso nativo sem traduzir para uma classe. Modules fornece escopo local com CSS familiar. Cada ganho responde a uma dor diferente.

O custo de dependência também importa. Tailwind adiciona uma etapa de build e uma versão a manter. CSS puro ainda passa por bundler em muitos projetos, mas pode funcionar sem ele. Modules depende da transformação do framework. Em uma página estática duradoura, menos pipeline pode valer mais que uma API extensa.

Cascata e override

CSS puro dá controle direto de seletor, camada e ordem. Modules reduz colisões de nome, mas a cascata ainda existe. Tailwind organiza utilities em layers e mantém especificidade baixa; duas classes conflitantes ainda dependem da ordem gerada.

html
<div class="p-2 p-8">...</div>

Não conte com a posição no atributo. Em API React que aceita override, tailwind-merge pode reduzir o conflito para p-8, como mostra a lição de componentes Tailwind. No CSS puro, uma convenção de layers e escopo cumpre papel equivalente em outro nível.

O custo humano de apagar estilo

Tailwind detecta uso por texto. Quando uma classe desaparece de todas as fontes, a regra deixa o bundle. CSS puro e Modules dependem de o seletor ser removido; bundlers não conseguem provar com segurança que todo CSS global está morto.

Por outro lado, uma utility pode estar numa string estática reservada para um estado raro e continuar gerada mesmo sem aparecer na sessão de teste. CSS Modules cria vínculo de import mais explícito, mas um módulo inteiro importado continua no grafo. Em todos os casos, cobertura de CSS é indício, não verdade absoluta.

js
const tons = {
  sucesso: 'bg-emerald-600',
  erro: 'bg-rose-600',
};

Manter opções estáticas é necessário para detecção; remova variantes que o produto abandonou. Uma API de componente pequena ajuda mais que qualquer minificador.

Como projetar uma comparação maior sem favorecer uma opção

O próximo experimento deve começar por uma especificação de interface, não pelo código de uma das variantes. Liste as mesmas quatorze seções, os mesmos estados, breakpoints, tokens e critérios de acessibilidade. Defina também quais recursos ficam fora. Se a versão Tailwind usa dark mode e a versão CSS não, bytes deixam de comparar a mesma entrega.

Escolha conteúdo congelado. Títulos, imagens e mensagens de erro precisam ser idênticos, porque uma linha extra pode ativar layout e utilidades diferentes. Use assets locais e desative downloads durante o build. Fixe versões no lockfile e registre sistema operacional e arquitetura da máquina.

Separe três conjuntos de métricas. O primeiro é transferência: CSS, HTML ou JavaScript, gzip, Brotli e cache entre páginas. O segundo é build: primeira compilação, rebuild, uso de memória e variância em várias execuções. O terceiro é manutenção: tempo para alterar tema, remover seção, adicionar estado e localizar regra vencedora. Misturar os três num único placar esconde o trade-off.

Para tempo, aqueça as ferramentas e execute uma quantidade suficiente para calcular mediana e percentis. Uma média pode ser puxada por um processo do sistema. Rebuild precisa manter o watcher vivo; iniciar o processo toda vez mede startup, não feedback durante desenvolvimento. Rode as alternativas em ordem alternada para reduzir efeito de cache e temperatura.

Para tamanho, compare artefatos servidos. Source map não entra no payload de produção se não é publicado; não some por acidente. CSS crítico inline precisa entrar no HTML. Classe repetida em JSX entra no JavaScript transformado. O cache compartilhado entre rotas pode favorecer uma folha global, enquanto CSS por rota reduz primeiro acesso. Registre os dois cenários.

Para manutenção, use tarefas cegas com critérios claros. “Trocar a ação primária e garantir contraste nos dois temas” é melhor que “editar o botão”. Conte tempo, arquivos tocados, regressões e perguntas necessárias. Não peça a uma pessoa especialista em Tailwind para representar CSS Modules e trate a diferença como propriedade da ferramenta; familiaridade é uma variável do estudo.

Faça revisão cruzada sem dizer qual resultado você espera. Uma implementação pode ficar artificialmente ruim porque tenta imitar a arquitetura da outra. CSS puro deve usar tokens e layers de forma competente; Modules deve aproveitar escopo; Tailwind deve usar componentes e tema. Comparar uma versão madura com duas versões ingênuas só mede experiência do autor.

Ao publicar resultados, mantenha as planilhas e scripts junto do commit. Informe mediana, dispersão e data. Uma atualização de bundler ou framework pode mudar os números. Conclusões precisam ser condicionais: “neste projeto, com estas fontes” em vez de “Tailwind sempre”.

Curva de crescimento e ponto de cruzamento

O cartão medido não encontra um ponto de cruzamento; ele só revela o piso. Para estudar crescimento, adicione uma seção por rodada e registre o delta, não apenas o total. Uma nova seção que reutiliza todas as utilities pode aumentar markup sem aumentar CSS Tailwind. Uma seção que introduz animação, filtro e nova paleta ativa infraestrutura maior.

CSS puro também tem degraus. Uma seção pode reutilizar .cartao e acrescentar quase nada, ou exigir um bloco inteiro de seletores. Modules pode duplicar declarações em módulos separados se o time não extrai tokens e primitivas. Por isso, ajuste uma linha à curva e inspecione os saltos; um único total final esconde onde cada abordagem ficou cara.

Observe ainda a remoção. Apague a terceira, a sétima e a décima quarta seção em rodadas separadas. Tailwind deixa de gerar utilities sem outros usos. CSS manual precisa que o seletor morto seja removido. Modules pode sair do grafo quando o import desaparece. Esse teste mede reversibilidade, uma qualidade importante em produto que experimenta muito.

Não procure necessariamente um vencedor global. Pode existir uma faixa em que CSS puro é menor, outra em que as diferenças comprimidas ficam irrelevantes e uma em que o custo de manutenção domina. A decisão profissional escolhe a faixa do produto esperado e reconhece incerteza.

Tabela de decisão

situação Tailwind CSS puro CSS Modules
página pequena sem build adiciona infraestrutura escolha direta geralmente indisponível sem bundler
app com muitos componentes utilities e tokens escalam bem exige convenção forte escopo local ajuda
design muito experimental valores arbitrários e CSS coexistem liberdade total liberdade com escopo
migração gradual pode entrar por componente já é a base bom para preservar legado
biblioteca pública exige contrato de fontes ou CSS pronto interoperável hashes e bundler exigem contrato
equipe iniciante em CSS nomes ajudam, mas podem esconder mecanismo ensina mecanismo diretamente adiciona conceito de módulo

Eu escolheria CSS puro para uma página pequena, estável e sem pipeline. Tailwind para uma aplicação de produto com identidade própria, muitos estados e equipe disposta a padronizar. Modules para uma base React existente que já usa escopo local e não ganha nada com migração total.

A lição de @apply mostra que sequer dentro do Tailwind existe uma única forma de organizar. Medimos quatro botões e @apply ficou 31 bytes gzip menor; a decisão continuou dependendo de fronteira e manutenção.

Decisão prática, não torcida

Pegue uma tela representativa do seu produto, implemente um componente nas duas opções finalistas e meça bundle, tempo de alteração e clareza da revisão. Não porte apenas o botão mais simples nem a tela mais excepcional. Documente o que ficou fora.

Se Tailwind vencer, siga o guia completo e defina tokens antes de espalhar valores arbitrários. Se CSS comum vencer, mantenha uma arquitetura de cascade layers e tokens. Se Modules vencer, estabeleça como temas globais entram. O próximo passo é um commit com o experimento e os números, não uma escolha baseada em preferência de rede social.

  • tailwind
  • css puro
  • css modules
  • benchmark
  • bundle
  • performance

Perguntas frequentes

Tailwind sempre gera mais CSS que CSS escrito à mão?
Não. Num componente minúsculo, tema e Preflight criam um piso maior. Num produto com muitas telas, utilitários são deduplicados e a curva muda. Meça um artefato representativo.
CSS Modules melhora o tamanho do CSS?
O objetivo principal é escopo local, não compressão. No teste pequeno, o hash aumentou alguns bytes. Em projeto real, estrutura dos seletores e remoção de código determinam mais.
Posso combinar Tailwind e CSS Modules?
Pode, mas evite duplicar responsabilidade. Use Modules legados onde já funcionam e utilities no markup novo, com regra clara para tokens, precedência e remoção gradual.
Tempo de build deve decidir a escolha?
Só se medido no projeto e no fluxo de trabalho real. Cache, plugins, quantidade de fontes e máquina alteram o resultado; uma execução única de exemplo não sustenta uma decisão de equipe.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em @tailwindcss/cli 4.3.3, Vite 8.2.2, gzip -9 e Brotli do Node 26.3.0, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. Tailwind CSS — Compatibility — tailwindcss.com
  2. Vite — CSS Modules — vite.dev
  3. MDN — CSS cascade — developer.mozilla.org

Continue por aqui