Shopping cart

Subtotal $0.00

View cartCheckout

Building better devs

TnewsTnews
Programação

Adeus Tailwind CSS: Como Estruturar Seu CSS Sem Framework em 2026

Email : 16

Julia Evans — a mesma que escreveu aquele zine sobre debugging que todo mundo compartilha — postou um artigo essa semana que explodiu no Hacker News. O título? “Moving Away from Tailwind”. E o timing não podia ser melhor, porque eu já vinha pensando a mesma coisa há meses.

O post dela atingiu 575 pontos e ficou no topo do HN por horas. Não é pouca coisa. Quando a comunidade dev mais exigente da internet valida um sentimento, vale prestar atenção.

Mas antes que você feche essa aba pensando “mais um hater de Tailwind” — não é isso. O ponto aqui é outro: CSS nativo em 2026 evoluiu tanto que, para muitos projetos, Tailwind virou peso morto. E a Julia mostrou exatamente como se livrar dele sem virar o caos.

O Elefante na Sala: 2.8MB de Classes Utility

O que motivou a Julia a sair do Tailwind? Um número: 2.8MB. Esse era o tamanho dos arquivos CSS dos projetos dela. Cerca de 270KB comprimidos com gzip, mas ainda assim absurdo para sites estáticos.

Por que isso aconteceu? Porque ela fazia o que 90% dos devs fazem — copiava o arquivo completo do Tailwind para cada projeto em vez de configurar um build system. E sinceramente, quem nunca?

O Tailwind 4 melhorou muito com a engine Oxide escrita em Rust (builds completos 5x mais rápidos, incrementais 100x mais rápidos). Mas o ponto fundamental permanece: você precisa de um build system. Sem ele, não dá para usar versões modernas do Tailwind. E para muitos projetos — blogs, landing pages, sites pessoais, documentação — isso é overengineering.


# O setup mínimo do Tailwind 4
npm install tailwindcss @tailwindcss/cli
npx @tailwindcss/cli -i input.css -o output.css --watch

# Versus CSS nativo
# Nenhuma dependência. Zero config. Abre o arquivo e escreve.

A pergunta que a Julia fez — e que eu repito aqui — é simples: se você precisa de um pipeline de build para escrever CSS, será que o CSS nativo não resolve sozinho?

CSS Nativo em 2026: O Jogo Mudou

Se a última vez que você olhou para CSS “puro” foi em 2020, prepare-se para levar um susto. O CSS de 2026 não tem nada a ver com aquele CSS que fazia todo mundo correr para o Sass.

Nesting Nativo

Suportado em Chrome 120+, Firefox 117+, Safari 17.2+ — mais de 90% dos navegadores. Sem Sass, sem PostCSS, sem nada:


.card {
  background: var(--surface);
  border-radius: 8px;
  padding: 1.5rem;

  & .title {
    font-size: var(--size-lg);
    font-weight: 600;
  }

  & .body {
    color: var(--text-muted);
    line-height: 1.6;
  }

  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  }
}

Isso roda no navegador. Direto. Sem transpilação.

Cascade Layers (@layer)

Quer controle total sobre a ordem de precedência dos seus estilos? @layer resolve:


@layer reset, base, components, utilities;

@layer reset {
  *, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
  }
}

@layer base {
  body {
    font-family: system-ui, sans-serif;
    color: var(--text);
    background: var(--bg);
  }
}

@layer components {
  .btn {
    padding: 0.5rem 1rem;
    border-radius: 4px;
    font-weight: 500;
  }
}

A primeira linha define a ordem. Estilos na layer utilities sempre vencem components, que sempre vencem base. Acabou aquela guerra de especificidade que fazia todo mundo enfiar !important em tudo.

Suporte em todos os browsers modernos: 96%+ global. É baseline.

Container Queries

Media queries olham para a janela do navegador. Container queries olham para o elemento pai:


.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

@container sidebar (min-width: 400px) {
  .widget {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}

Componentes que se adaptam ao espaço disponível, não ao tamanho da tela. Isso é algo que Tailwind simplesmente não faz — pelo menos não sem gambiarras.

O Seletor :has()

Esse aqui é o “parent selector” que a comunidade CSS pediu por 20 anos:


/* Estiliza o form que contém um input inválido */
form:has(input:invalid) {
  border-color: var(--error);
}

/* Card que tem imagem recebe layout diferente */
.card:has(img) {
  grid-template-rows: 200px 1fr;
}

Cada uma dessas features individualmente já seria motivo para repensar se você precisa de um framework. Juntas, elas eliminam uns 70% dos motivos pelos quais as pessoas adotaram Tailwind.

A Arquitetura da Julia Evans (Que Funciona)

O mais interessante do post da Julia não foi o “adeus Tailwind”. Foi o sistema que ela montou para substituí-lo. Nove decisões organizacionais que, juntas, formam uma arquitetura CSS coerente.

1. Um Componente, Um Arquivo

Cada componente visual tem seu próprio arquivo CSS. O componente .zine vive em zine.css. O componente .nav vive em nav.css. Zero risco de um estilo vazar para outro componente:


/* zine.css */
.zine {
  display: grid;
  gap: 1rem;

  &.horizontal {
    grid-template-columns: 1fr 1fr;
  }

  &.vertical {
    grid-template-rows: auto 1fr;
  }
}

Parece óbvio, né? Mas quantos projetos você já viu com um styles.css de 3.000 linhas?

2. Design Tokens como CSS Custom Properties

Em vez de classes como text-pink-500 ou bg-slate-100, variáveis nativas:


:root {
  /* Cores */
  --pink: #fea0c2;
  --blue: #3b82f6;
  --text: #1a1a2e;
  --text-muted: #6b7280;
  --surface: #ffffff;
  --bg: #f8fafc;

  /* Tipografia */
  --size-sm: 0.875rem;
  --size-base: 1rem;
  --size-lg: 1.25rem;
  --size-xl: 1.5rem;
  --size-2xl: 2rem;

  /* Line heights pareados */
  --line-height-sm: 1.25rem;
  --line-height-base: 1.5rem;
  --line-height-lg: 1.75rem;
}

A sacada aqui é que cada --size-<em> tem um --line-height-</em> correspondente. Você nunca precisa pensar “qual line-height combina com esse font-size?” — já está definido.

3. O Owl Selector para Espaçamento

Essa técnica tem nome engraçado — “lobotomized owl selector” — mas é genial. Em vez de adicionar mb-4 em cada elemento, você define o espaçamento entre irmãos:


section > * + * {
  margin-top: 1rem;
}

.stack-lg > * + * {
  margin-top: 2rem;
}

.stack-sm > * + * {
  margin-top: 0.5rem;
}

O seletor <em> + </em> significa “qualquer elemento que vem depois de outro elemento”. Simples, previsível, zero classes repetitivas.

Na prática, em vez de escrever isso no HTML:


<!-- Tailwind -->
<div>
  <p class="mb-4">Primeiro parágrafo</p>
  <p class="mb-4">Segundo parágrafo</p>
  <p>Terceiro parágrafo</p>
</div>

Você escreve isso:


<!-- CSS puro com owl selector -->
<section>
  <p>Primeiro parágrafo</p>
  <p>Segundo parágrafo</p>
  <p>Terceiro parágrafo</p>
</section>

HTML limpo. Espaçamento automático. Uma regra CSS cuida de tudo.

4. Grid Responsivo Sem Media Queries

A Julia usa um pattern de CSS Grid que se adapta automaticamente:


.grid-auto {
  display: grid;
  grid-template-columns: repeat(
    auto-fit,
    minmax(min(100%, 400px), max-content)
  );
  gap: 1.5rem;
}

Essa única regra cria um grid que:

  • Mostra colunas lado a lado quando há espaço
  • Empilha em uma coluna quando a tela é pequena
  • Não precisa de nenhuma media query

Compare com Tailwind, onde você precisa de grid-cols-1 md:grid-cols-2 lg:grid-cols-3. Três classes para algo que uma propriedade CSS resolve sozinha.

O Reset Roubado do Tailwind

Aqui tem uma ironia deliciosa: a Julia copiou o reset CSS do próprio Tailwind — o arquivo preflight.css. É um reset moderno e bem pensado, e ela não viu motivo para reinventar a roda.


/* Trechos do preflight adaptado */
*, *::before, *::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

img, picture, video, canvas, svg {
  display: block;
  max-width: 100%;
}

input, button, textarea, select {
  font: inherit;
}

Isso é pragmatismo puro. Você não precisa amar ou odiar um framework — use o que funciona, descarte o resto.

Build System Mínimo: esbuild

A Julia usa esbuild para produção. Não Webpack. Não Vite (apesar de que Vite é ótimo). O esbuild porque é absurdamente rápido e faz o mínimo necessário:


# Build de produção
npx esbuild styles/main.css --bundle --minify --outfile=dist/styles.css

# Em dev, usa @import nativo do browser
# Nenhum watcher, nenhum HMR, nenhuma config

Em desenvolvimento, ela simplesmente usa @import nativo. O navegador carrega cada arquivo CSS separadamente. É mais lento? Sim. Para um blog? Não faz a menor diferença.


/* main.css */
@import "./reset.css";
@import "./variables.css";
@import "./base.css";
@import "./components/nav.css";
@import "./components/card.css";
@import "./components/zine.css";

Para produção, o esbuild resolve os imports e entrega um arquivo único, minificado. Setup de 30 segundos.

Quando Tailwind Ainda Faz Sentido

Eu não seria honesto se dissesse que Tailwind morreu ou que todo mundo deveria sair correndo. Tem cenários onde ele continua sendo a melhor escolha:

Cenário Tailwind? CSS Puro?
——— ———– ———–
App React/Vue grande com time Sim Talvez
Landing page ou blog Não Sim
Design system corporativo Sim Depende
Protótipo rápido Sim Não
Site estático com poucas páginas Não Sim
Projeto com muitos devs juniores Sim Não

O Tailwind brilha quando você precisa de consistência forçada em times grandes. As classes utilitárias são constraints — elas impedem que cada dev invente seu próprio sistema de espaçamento. Para uma pessoa sozinha ou times pequenos que sabem CSS, essas constraints viram algemas.

E tem o argumento da Julia que me pegou de jeito: Tailwind inadvertidamente ensina CSS systems thinking. As decisões de design do framework — escala de cores, espaçamento consistente, design tokens — são boas práticas que você pode (e deve) roubar e aplicar no CSS nativo.

Como Migrar: Passo a Passo Real

Se você quer sair do Tailwind em um projeto existente, aqui vai um roteiro que funciona:

Fase 1: Extraia seus tokens


# Liste todas as classes Tailwind que você usa
grep -roh 'class="[^"]*"' src/ | \
  tr ' ' '\n' | \
  sort | uniq -c | sort -rn | head -30

Isso mostra quais classes aparecem mais. Provavelmente flex, p-4, text-sm, bg-white. Cada uma dessas vira uma variável ou regra CSS.

Fase 2: Crie seu sistema de variáveis

Pegue a escala de cores e espaçamento do seu tailwind.config.js e converta para custom properties:


:root {
  /* Espaçamento (mesma escala do Tailwind) */
  --space-1: 0.25rem;  /* p-1 */
  --space-2: 0.5rem;   /* p-2 */
  --space-4: 1rem;     /* p-4 */
  --space-6: 1.5rem;   /* p-6 */
  --space-8: 2rem;     /* p-8 */
}

Fase 3: Migre componente por componente

Não tente migrar tudo de uma vez. Pegue um componente, extraia as classes Tailwind para um arquivo CSS, teste, e siga para o próximo:


<!-- Antes (Tailwind) -->
<div class="flex items-center gap-4 p-6 bg-white rounded-lg shadow-md">
  <img class="w-12 h-12 rounded-full" src="avatar.jpg" alt="">
  <div>
    <h3 class="text-lg font-semibold text-gray-900">Nome</h3>
    <p class="text-sm text-gray-500">Descrição</p>
  </div>
</div>

<!-- Depois (CSS puro) -->
<div class="user-card">
  <img class="user-card__avatar" src="avatar.jpg" alt="">
  <div>
    <h3 class="user-card__name">Nome</h3>
    <p class="user-card__desc">Descrição</p>
  </div>
</div>


/* user-card.css */
.user-card {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-6);
  background: var(--surface);
  border-radius: 8px;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);

  &__avatar {
    width: 3rem;
    height: 3rem;
    border-radius: 50%;
  }

  &__name {
    font-size: var(--size-lg);
    font-weight: 600;
    color: var(--text);
  }

  &__desc {
    font-size: var(--size-sm);
    color: var(--text-muted);
  }
}

Fase 4: Delete o Tailwind


npm uninstall tailwindcss @tailwindcss/cli
rm tailwind.config.js
# Pronto.

O Que o CSS Ainda Não Resolve

Vou ser justo. Tem coisas que Tailwind faz melhor que CSS puro, pelo menos por enquanto:

Purging automático. O Tailwind remove automaticamente classes não usadas do bundle final. Com CSS puro, você precisa de ferramentas externas como PurgeCSS — ou simplesmente não se preocupar, porque seu CSS provavelmente tem menos de 20KB se for escrito por componente.

Dark mode. A classe dark: do Tailwind é extremamente conveniente. Com CSS nativo, você precisa de @media (prefers-color-scheme: dark) ou uma classe no :


:root {
  --bg: #ffffff;
  --text: #1a1a2e;
}

[data-theme="dark"] {
  --bg: #1a1a2e;
  --text: #e2e8f0;
}

Funciona perfeitamente, mas você precisa do JavaScript para o toggle manual.

Autocomplete na IDE. O Tailwind IntelliSense para VS Code é fantástico. CSS puro com custom properties tem autocomplete, mas não é tão polido. Isso faz diferença no dia a dia.

A Verdade que Ninguém Quer Ouvir

A Julia disse algo no post dela que merece ser repetido: “CSS é difícil porque resolve um problema difícil.”

Frameworks como Tailwind não eliminam a complexidade do CSS — eles escondem ela atrás de classes. E quando você precisa fazer algo que o framework não previu (e você vai precisar), a abstração vaza e você fica pior do que se tivesse aprendido CSS de verdade.

O CSS de 2026 não é o CSS de 2016. Nesting, layers, container queries, :has(), custom properties com fallbacks, color-mix(), subgrid — o gap entre CSS nativo e “CSS com framework” nunca foi tão pequeno.

Se você está começando um projeto novo e tem liberdade de escolha, vale a pena se perguntar: eu realmente preciso de 200KB de classes utilitárias, um build pipeline, e uma dependência NPM… ou 50 linhas de custom properties e um pouco de organização resolvem?

A resposta da Julia Evans, da comunidade do Hacker News, e provavelmente a sua, se você testar por uma semana: não, você não precisa.

Fonte de inspiração: Moving away from Tailwind, and learning to structure my CSS — Julia Evans

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts