Vou dizer algo que pode irritar algumas pessoas: parei de usar Tailwind CSS em novos projetos no final de 2025, e não olhei para trás. Não porque Tailwind seja ruim -- é genuinamente excelente no que faz. Mas porque o CSS vanilla ficou tão bom que as desvantagens não fazem mais sentido para os tipos de projetos que construímos.

Design tokens estão no coração dessa mudança. A ideia de armazenar suas decisões de design como variáveis reutilizáveis e independentes de plataforma não é nova. Mas o ferramental e o suporte do navegador para propriedades customizadas CSS em 2026 amadureceram ao ponto onde você pode construir um sistema completo de design tokens com zero etapas de build, zero dependências e zero memorização de classe utilitária. Deixe-me mostrar como.

Table of Contents

Vanilla CSS Design Tokens Without Tailwind in 2026

O Que Design Tokens Realmente São

Design tokens são os valores atômicos que definem sua linguagem visual. Cores, espaçamento, tipografia, sombras, bordas arredondadas, durações de animação -- qualquer coisa que represente uma decisão de design. O termo foi cunhado pelo time de design da Salesforce em 2014, mas o conceito evoluiu significativamente.

Aqui está o insight chave que a maioria das pessoas perde: design tokens não são apenas variáveis. Eles são um contrato entre design e engenharia. Quando seu designer diz "use a cor de ação primária", isso mapeia para um token, não para um valor hex. Quando ele diz "espaçamento médio", isso também é um token.

O W3C Design Tokens Community Group publicou a especificação do Design Tokens Format Module, e embora ainda esteja evoluindo, as ideias centrais se solidificaram. Tokens existem em camadas:

  1. Tokens primitivos (valores brutos como #1a73e8 ou 16px)
  2. Tokens semânticos (aliases orientados por propósito como color-action-primary)
  3. Tokens de componente (escopo específico para elementos de UI como button-background)

Esta arquitetura de três camadas é o que torna sistemas em larga escala gerenciáveis. Propriedades customizadas CSS mapeiam perfeitamente para ela.

Por Que CSS Vanilla Me Conquistou

Seja honesto sobre minha jornada. Usei Tailwind felizmente por anos. Construí sites em produção com ele. Recomendei para clientes. Mas várias coisas mudaram:

CSS alcançou o nível. Nesting, :has(), container queries, @layer, @scope, color-mix(), oklch(), sintaxe de cor relativa -- esses não são recursos experimentais mais. Eles são baseline em todo navegador maior em 2026. A lacuna que frameworks utilitários preenchiam? Diminuiu dramaticamente.

Bundle size importa mais do que nunca. Com Core Web Vitals impactando diretamente rankings de busca, cada kilobyte conta. Um sistema de token CSS vanilla bem estruturado pesa quase nada. O compilador JIT do Tailwind é inteligente, mas ainda envia CSS para cada utilitário que você usa.

Custo de manutenção é real. Herdei projetos Tailwind onde componentes têm 30+ classes utilitárias encadeadas. Ler className="flex items-center justify-between px-4 py-2 bg-white dark:bg-gray-800 rounded-lg shadow-sm hover:shadow-md transition-shadow duration-200 border border-gray-200 dark:border-gray-700" não é divertido. Com design tokens e uma camada fina de CSS, a intenção é mais clara.

Handoff design-engenharia melhorou. Quando tokens em CSS correspondem a tokens em Figma 1:1, conversas ficam precisas. Sem mais "qual classe Tailwind mapeia para essa variável Figma?".

Isso não é uma guerra santa. Se Tailwind funciona para seu time, continue usando. Mas se você está iniciando algo novo e quer máximo controle com mínima abstração, tokens CSS vanilla valem séria consideração.

Configurando Sua Arquitetura de Token

Vamos construir isso do zero. Vou usar a estrutura de arquivo que refinamos através de múltiplos projetos de headless CMS na Social Animal, mas adapte para suas necessidades.

O princípio central: tokens fluem para baixo através de camadas de especificidade.

tokens/
├── primitives.css       /* Valores brutos */
├── semantic.css         /* Aliases orientados por propósito */
├── components/
│   ├── button.css
│   ├── card.css
│   └── input.css
├── themes/
│   ├── light.css
│   └── dark.css
└── index.css            /* Orquestração de import */

Seu index.css usa @layer para estabelecer uma cascata clara:

@layer primitives, semantic, themes, components;

@import './primitives.css' layer(primitives);
@import './semantic.css' layer(semantic);
@import './themes/light.css' layer(themes);
@import './components/button.css' layer(components);
@import './components/card.css' layer(components);
@import './components/input.css' layer(components);

Isso é importante. @layer fornece controle explícito sobre a cascata sem lutar contra especificidade. Primitivos são sobrescrito por tokens semânticos, que são sobrescritos por temas, que são sobrescritos por componentes. Hierarquia limpa.

Vanilla CSS Design Tokens Without Tailwind in 2026 - architecture

Tokens Primitivos: Seus Valores Brutos

Primitivos são sua paleta. Eles não devem ser usados diretamente em componentes -- pense neles como a tinta em sua prateleira antes de decidir o que pintar.

/* primitives.css */
:root {
  /* Cores usando OKLCH para paletas perceptualmente uniformes */
  --color-blue-50: oklch(0.97 0.01 250);
  --color-blue-100: oklch(0.93 0.03 250);
  --color-blue-200: oklch(0.87 0.06 250);
  --color-blue-300: oklch(0.78 0.10 250);
  --color-blue-400: oklch(0.68 0.15 250);
  --color-blue-500: oklch(0.58 0.19 250);
  --color-blue-600: oklch(0.50 0.19 250);
  --color-blue-700: oklch(0.42 0.17 250);
  --color-blue-800: oklch(0.35 0.14 250);
  --color-blue-900: oklch(0.27 0.10 250);

  /* Escala de espaçamento (modular, baseado em grid 4px) */
  --space-1: 0.25rem;   /* 4px */
  --space-2: 0.5rem;    /* 8px */
  --space-3: 0.75rem;   /* 12px */
  --space-4: 1rem;      /* 16px */
  --space-5: 1.25rem;   /* 20px */
  --space-6: 1.5rem;    /* 24px */
  --space-8: 2rem;      /* 32px */
  --space-10: 2.5rem;   /* 40px */
  --space-12: 3rem;     /* 48px */
  --space-16: 4rem;     /* 64px */
  --space-20: 5rem;     /* 80px */
  --space-24: 6rem;     /* 96px */

  /* Escala de tipografia */
  --font-size-xs: 0.75rem;
  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-md: 1.125rem;
  --font-size-lg: 1.25rem;
  --font-size-xl: 1.5rem;
  --font-size-2xl: 1.875rem;
  --font-size-3xl: 2.25rem;
  --font-size-4xl: 3rem;

  /* Pesos de fonte */
  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;

  /* Border radius */
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  --radius-xl: 1rem;
  --radius-full: 9999px;

  /* Sombras */
  --shadow-sm: 0 1px 2px oklch(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px oklch(0 0 0 / 0.07);
  --shadow-lg: 0 10px 15px oklch(0 0 0 / 0.1);
  --shadow-xl: 0 20px 25px oklch(0 0 0 / 0.1);

  /* Durações */
  --duration-fast: 100ms;
  --duration-normal: 200ms;
  --duration-slow: 300ms;
  --duration-slower: 500ms;
}

Estou usando OKLCH aqui porque é um espaço de cor genuinamente melhor. Diferente de HSL, leveza em OKLCH é perceptualmente uniforme -- uma leveza de 0.5 realmente parece cinza médio independente de matiz. Suas escalas de cor parecem consistentes através do espectro. Todo navegador maior suporta isso agora.

Tokens Semânticos: Onde a Mágica Acontece

Tokens semânticos referenciam primitivos e dão-lhes significado. Esta é a camada que torna theming possível e torna seu CSS autodocumentado.

/* semantic.css */
:root {
  /* Cores de superfície */
  --color-surface-primary: var(--color-white, #fff);
  --color-surface-secondary: var(--color-gray-50);
  --color-surface-tertiary: var(--color-gray-100);
  --color-surface-inverse: var(--color-gray-900);

  /* Cores de texto */
  --color-text-primary: var(--color-gray-900);
  --color-text-secondary: var(--color-gray-600);
  --color-text-tertiary: var(--color-gray-400);
  --color-text-inverse: var(--color-white, #fff);
  --color-text-link: var(--color-blue-600);
  --color-text-link-hover: var(--color-blue-700);

  /* Cores de ação */
  --color-action-primary: var(--color-blue-600);
  --color-action-primary-hover: var(--color-blue-700);
  --color-action-primary-active: var(--color-blue-800);

  /* Cores de feedback */
  --color-feedback-success: var(--color-green-600);
  --color-feedback-warning: var(--color-amber-500);
  --color-feedback-error: var(--color-red-600);
  --color-feedback-info: var(--color-blue-500);

  /* Cores de border */
  --color-border-primary: var(--color-gray-200);
  --color-border-secondary: var(--color-gray-100);
  --color-border-focus: var(--color-blue-500);

  /* Espaçamento semântico */
  --space-inline-xs: var(--space-1);
  --space-inline-sm: var(--space-2);
  --space-inline-md: var(--space-4);
  --space-inline-lg: var(--space-6);
  --space-stack-xs: var(--space-1);
  --space-stack-sm: var(--space-2);
  --space-stack-md: var(--space-4);
  --space-stack-lg: var(--space-8);
  --space-stack-xl: var(--space-12);
}

Note como nada aqui é um valor codificado. Tudo referencia primitivos. Se você quer rebrand amanhã, você muda primitivos. Se você quer ajustar como "ação primária" se sente, você muda tokens semânticos. O código do componente nunca precisa saber.

Tokens de Componente: A Camada Final

Tokens de componente escopo decisões de design para elementos UI específicos. Isso é opcional para projetos pequenos mas essencial para qualquer coisa com um sistema de design.

/* components/button.css */
.button {
  --_bg: var(--color-action-primary);
  --_bg-hover: var(--color-action-primary-hover);
  --_bg-active: var(--color-action-primary-active);
  --_text: var(--color-text-inverse);
  --_radius: var(--radius-md);
  --_padding-block: var(--space-2);
  --_padding-inline: var(--space-4);
  --_font-size: var(--font-size-sm);
  --_font-weight: var(--font-weight-semibold);
  --_shadow: var(--shadow-sm);
  --_transition: var(--duration-normal);

  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--_padding-block) var(--_padding-inline);
  background: var(--_bg);
  color: var(--_text);
  font-size: var(--_font-size);
  font-weight: var(--_font-weight);
  border-radius: var(--_radius);
  box-shadow: var(--_shadow);
  transition: background var(--_transition), box-shadow var(--_transition);
  border: none;
  cursor: pointer;

  &:hover {
    background: var(--_bg-hover);
    box-shadow: var(--shadow-md);
  }

  &:active {
    background: var(--_bg-active);
  }

  &.--secondary {
    --_bg: transparent;
    --_text: var(--color-action-primary);
    --_shadow: none;
    border: 1px solid var(--color-border-primary);

    &:hover {
      --_bg: var(--color-surface-secondary);
    }
  }

  &.--ghost {
    --_bg: transparent;
    --_text: var(--color-text-primary);
    --_shadow: none;

    &:hover {
      --_bg: var(--color-surface-secondary);
    }
  }
}

A convenção de prefixo --_ (com underscore) sinaliza tokens de componente "privados". Isso não é enforçado pelo navegador, mas é um sinal claro para outros desenvolvedores: esses tokens são internos a esse componente.

Também -- veja como CSS nesting funciona aqui. Sem preprocessador necessário. Isso é CSS nativo em 2026.

Dark Mode e Theming Sem Framework

Aqui é onde a arquitetura de token realmente se destaca. Dark mode se torna uma questão de reatribuir tokens semânticos.

/* themes/dark.css */
@media (prefers-color-scheme: dark) {
  :root {
    --color-surface-primary: var(--color-gray-900);
    --color-surface-secondary: var(--color-gray-800);
    --color-surface-tertiary: var(--color-gray-700);
    --color-surface-inverse: var(--color-white, #fff);

    --color-text-primary: var(--color-gray-50);
    --color-text-secondary: var(--color-gray-300);
    --color-text-tertiary: var(--color-gray-500);
    --color-text-inverse: var(--color-gray-900);
    --color-text-link: var(--color-blue-400);
    --color-text-link-hover: var(--color-blue-300);

    --color-border-primary: var(--color-gray-700);
    --color-border-secondary: var(--color-gray-800);

    --shadow-sm: 0 1px 2px oklch(0 0 0 / 0.2);
    --shadow-md: 0 4px 6px oklch(0 0 0 / 0.3);
    --shadow-lg: 0 10px 15px oklch(0 0 0 / 0.4);
  }
}

/* Suporte para toggle manual */
[data-theme="dark"] {
  --color-surface-primary: var(--color-gray-900);
  /* ... mesmas sobreposições ... */
}

Seus componentes não mudam nada. Zero classes de dark mode. Zero lógica condicional. Os tokens lidam com tudo.

Quer um tema de marca para um cliente white-label? Mesmo padrão:

[data-theme="client-acme"] {
  --color-blue-500: oklch(0.55 0.20 280); /* Seu roxo em vez de azul */
  --color-action-primary: var(--color-blue-500);
  --radius-md: 1rem; /* Eles gostam de cantos mais arredondados */
}

Usamos essa abordagem extensivamente em nossos projetos de desenvolvimento Next.js onde theming multi-tenant é uma exigência comum.

Tokens Responsivos Com Container Queries

Isso é algo que você genuinamente não pode fazer com Tailwind (pelo menos não elegantemente). Container queries deixam você mudar valores de token baseado no tamanho do container, não apenas no viewport.

.card-grid {
  container-type: inline-size;
  container-name: card-grid;
}

@container card-grid (max-width: 500px) {
  .card {
    --_padding: var(--space-3);
    --_title-size: var(--font-size-base);
    --_gap: var(--space-2);
  }
}

@container card-grid (min-width: 501px) {
  .card {
    --_padding: var(--space-6);
    --_title-size: var(--font-size-lg);
    --_gap: var(--space-4);
  }
}

Componentes que respondem a seu próprio contexto em vez do viewport. Isso é particularmente poderoso quando construindo bibliotecas de componentes para sites Astro onde componentes são reutilizados através de layouts drasticamente diferentes.

Sincronizando Design Tokens Com Figma

Figma Variables (lançadas em 2023, significativamente expandidas desde) se alinham diretamente com nosso modelo de token de três camadas. Aqui está o workflow:

  1. Defina tokens em Figma usando seu painel Variables (primitivos → semânticos → componente)
  2. Exporte tokens usando o plugin Tokens Studio ou a REST API do Figma
  3. Transforme para CSS usando Style Dictionary ou as ferramentas mais novas de Cobalt UI
  4. Confirme em seu repo como seu diretório tokens/

Uma config Style Dictionary para saída CSS parece:

{
  "source": ["tokens/**/*.json"],
  "platforms": {
    "css": {
      "transformGroup": "css",
      "buildPath": "src/styles/tokens/",
      "files": [
        {
          "destination": "primitives.css",
          "format": "css/variables",
          "filter": { "filePath": "tokens/primitives" }
        },
        {
          "destination": "semantic.css",
          "format": "css/variables",
          "filter": { "filePath": "tokens/semantic" }
        }
      ]
    }
  }
}

O resultado é um pipeline de CI onde mudanças de design em Figma fluem em seus tokens CSS automaticamente. Sem tradução manual, sem deriva.

Comparação de Performance: CSS Vanilla vs Tailwind

Executei benchmarks em um site de marketing real que reconstruímos -- mesmo design, duas implementações. Aqui estão os números:

Métrica Tailwind v4 Tokens CSS Vanilla Diferença
Tamanho total de CSS (gzipped) 14.2 KB 6.8 KB -52%
First Contentful Paint 1.2s 1.0s -17%
Largest Contentful Paint 2.1s 1.8s -14%
Tempo de parse de CSS 3.2ms 1.4ms -56%
Tamanho de HTML (gzipped) 28.4 KB 22.1 KB -22%
Tempo de build 1.8s 0.4s* -78%

*CSS Vanilla com apenas bundling @import via Lightning CSS.

A diferença no tamanho de HTML é notável -- as classes utilitárias do Tailwind adicionam peso significativo para seu markup. Em uma página com 200+ elementos, essas strings de classe somam.

Dito isso, Tailwind v4 (lançado no início de 2025) fez grandes melhorias com seu motor Oxide. A lacuna diminuiu. Mas CSS vanilla com zero dependências de build é difícil de bater em performance bruta.

Estrutura de Arquivo Token do Mundo Real

Aqui está a estrutura real de um projeto recente -- um site de comércio headless construído com Next.js e Sanity:

src/styles/
├── tokens/
│   ├── primitives/
│   │   ├── colors.css
│   │   ├── spacing.css
│   │   ├── typography.css
│   │   ├── shadows.css
│   │   ├── borders.css
│   │   └── motion.css
│   ├── semantic/
│   │   ├── colors.css
│   │   ├── spacing.css
│   │   └── typography.css
│   ├── themes/
│   │   ├── light.css
│   │   ├── dark.css
│   │   └── high-contrast.css
│   └── index.css
├── components/
│   ├── button.css
│   ├── card.css
│   ├── input.css
│   ├── modal.css
│   ├── navigation.css
│   └── typography.css
├── layouts/
│   ├── grid.css
│   └── container.css
├── utilities/
│   └── helpers.css          /* Algumas classes utilitárias que realmente precisamos */
├── reset.css
└── main.css

O arquivo utilities/helpers.css é interessante -- ainda escrevo um punhado de classes utilitárias para padrões genuinamente reutilizáveis:

/* utilities/helpers.css */
.visually-hidden {
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

.flow > * + * {
  margin-block-start: var(--flow-space, var(--space-stack-md));
}

.cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--cluster-gap, var(--space-inline-md));
  align-items: center;
}

Note como até mesmo as utilitárias referenciam tokens. Tudo fica conectado.

Ferramental Que Torna Isso Prático

Você não precisa de muito, mas algumas ferramentas tornam o DX significativamente melhor:

Ferramenta Propósito Notas
Lightning CSS Bundling, minificação, prefixação de vendor Substitui PostCSS para a maioria dos casos. Incrivelmente rápido.
Style Dictionary 4 Transformação de formato de token Figma → CSS, iOS, Android de uma única fonte
Cobalt UI Ferramental de token W3C DTCG Alternativa mais nova a Style Dictionary, alinhada com spec
Tokens Studio Plugin Figma para gerenciar tokens Melhor integração Figma em sua classe.
CSS Utility Kit (VS Code) Autocompletar para propriedades customizadas Mostra valores de token ao passar mouse
Open Props Biblioteca de propriedade customizada CSS pré-feita Excelente ponto de partida se você não quer construir do zero

Lightning CSS merece um callout especial. Escrito em Rust, lida com inlining @import, compilação de nesting para navegadores mais antigos, e minificação em uma única passagem. É o que usamos na maioria de nossas compilações de headless CMS agora.

lightningcss --bundle --minify src/styles/main.css -o dist/styles.css

Isso é tudo. Sem config PostCSS. Sem corrente de plugin. Um comando.

FAQ

Posso usar design tokens sem uma etapa de build?

Absolutamente. Se você está visando navegadores modernos (e em 2026, você deveria estar), propriedades customizadas CSS nativas funcionam sem qualquer compilação. A única coisa que você perde é bundling @import em um arquivo único -- navegadores lidam com múltiplas importações de arquivo CSS perfeitamente, embora você queira fazer bundle para performance em produção. Lightning CSS ou até um simples comando cat podem lidar com isso.

Como tokens de design CSS vanilla se comparam com Tailwind v4?

Tailwind v4 realmente se moveu em direção a propriedades customizadas CSS internamente, o que valida a abordagem. A diferença está em experiência de desenvolvedor: Tailwind oferece classes utilitárias para aplicar em HTML, enquanto tokens vanilla oferecem variáveis de design para usar em seu próprio CSS. Tokens vanilla produzem CSS e saída de HTML menores, oferecem mais flexibilidade para theming, e não exigem aprender um sistema de nomenclatura de classe. Tailwind oferece prototipagem mais rápida e enforcement de consistência mais forte para times maiores.

E quanto a segurança de tipo TypeScript para design tokens?

Se você está usando CSS Modules ou CSS-in-JS, você pode gerar tipos TypeScript de seus arquivos de token. Style Dictionary 4 e Cobalt UI ambos suportam saída TypeScript. Para CSS puro, extensões VS Code como CSS Variable Autocomplete oferecem segurança em nível de editor com autocompletar e validação.

Deveria usar Open Props em vez de construir meus próprios tokens?

Open Props é um excelente ponto de partida, especialmente para protótipos ou projetos menores. Fornece um conjunto bem-desenhado de propriedades customizadas CSS pronto para uso. Para sistemas de design em produção, entretanto, você provavelmente vai querer definir seus próprios primitivos que correspondam sua marca. Você pode usar Open Props como referência e cherry-pick sua abordagem para coisas como funções de easing ou escalas de sombra.

Como manipulo design tokens em um monorepo com múltiplos apps?

Crie um pacote tokens compartilhado que exporte seus arquivos CSS. Cada app importa o pacote de token e pode adicionar suas próprias sobreposições de tema. Isso funciona lindamente com workspaces em pnpm ou npm. Fizemos isso para clientes de e-commerce multi-marca onde cada loja compartilha os mesmos tokens primitivos mas tem mappings semânticos únicos.

CSS vanilla é mais lento para desenvolver do que Tailwind?

Inicialmente, sim -- você está configurando infraestrutura que Tailwind oferece gratuitamente. Mas depois da primeira semana ou duas, velocidade é comparável ou mais rápida. Você não está trocando contexto entre HTML e uma referência de classe utilitária. Você está escrevendo CSS que lê como Inglês. E quando você precisa mudar algo, você muda em um lugar em vez de procurar cada instância de bg-blue-500.

Como convencer meu time a largar Tailwind?

Não enquadre como largar Tailwind -- enquadre como adotar design tokens. Comece introduzindo uma camada de token junto com Tailwind: defina suas propriedades customizadas, referencie-as em um tailwind.config theme. Ao longo do tempo, você pode descobrir que o sistema de token faz a maioria do trabalho e Tailwind se torna opcional. Deixe o time chegar àquela conclusão organicamente.

E quanto a bibliotecas de componente como Shadcn/UI que dependem de Tailwind?

Shadcn/UI é fantástico, mas sua dependência de Tailwind é detalhe de implementação, não arquitetura fundamental. Os padrões de componente e lógica de acessibilidade são o que importa. Vários forks da comunidade surgiram que usam tokens CSS vanilla em vez de classes Tailwind. Você também pode migrar gradualmente componentes Shadcn para usar seu sistema de token -- os primitivos subjacentes Radix UI não importam como você estiliza eles.

Posso usar essa abordagem com Astro ou Next.js?

Absolutamente. Ambos frameworks lidam com CSS vanilla nativo com zero configuração. Estilos escopo do Astro funcionam perfeitamente com propriedades customizadas CSS já que tokens definidos em :root naturalmente cascata em estilos de componente escopo. Next.js suporta CSS Modules junto com arquivos de token globais. Usamos esse setup exato em nossos projetos Next.js e compilações Astro regularmente. Se você está explorando isso para um novo projeto, entre em contato conosco -- ficamos felizes de compartilhar o que aprendemos.