Design System Moderno com Tailwind CSS v4
Construir um Design System consistente e escalável é fundamental para qualquer projeto sério de frontend. Com o lançamento do Tailwind CSS v4, temos novas ferramentas poderosas para criar sistemas de design que são ao mesmo tempo flexíveis e fáceis de manter. Neste guia completo, vamos explorar como construir um Design System profissional do zero.
O que mudou no Tailwind CSS v4?
O Tailwind v4 representa uma reescrita completa do framework, trazendo mudanças significativas:
Principais Novidades
| Feature | Tailwind v3 | Tailwind v4 |
|---|---|---|
| Engine | PostCSS | Oxide (Rust) |
| Config | tailwind.config.js | CSS @theme |
| Performance | Rápido | 10x mais rápido |
| CSS Variables | Manual | Automático |
| Container Queries | Plugin | Nativo |
"Tailwind v4 não é apenas mais rápido, é uma nova forma de pensar sobre configuração de estilos." - Adam Wathan, criador do Tailwind
Configuração com @theme
A grande mudança do v4 é a configuração via CSS usando a diretiva @theme:
Estrutura Básica
/* globals.css */
@import "tailwindcss";
@theme {
/* Colors - usando formato RGB sem vírgulas */
--color-brand-50: 240 253 244;
--color-brand-100: 220 252 231;
--color-brand-500: 34 197 94;
--color-brand-600: 22 163 74;
--color-brand-900: 20 83 45;
/* Secondary palette */
--color-accent-400: 250 204 21;
--color-accent-500: 234 179 8;
/* Neutral scale */
--color-neutral-50: 250 250 250;
--color-neutral-900: 23 23 23;
/* Spacing scale */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
--spacing-2xl: 3rem;
/* Typography */
--font-sans: "Inter", system-ui, sans-serif;
--font-mono: "JetBrains Mono", monospace;
/* Border radius */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}
Tokens Semânticos
Além dos tokens de base, crie tokens semânticos para contextos específicos:
@theme {
/* Tokens semânticos para temas */
--color-background: var(--color-neutral-50);
--color-foreground: var(--color-neutral-900);
--color-primary: var(--color-brand-500);
--color-primary-hover: var(--color-brand-600);
--color-muted: var(--color-neutral-100);
--color-border: var(--color-neutral-200);
/* Estados */
--color-success: 34 197 94;
--color-warning: 234 179 8;
--color-error: 239 68 68;
--color-info: 59 130 246;
}
/* Dark mode */
@media (prefers-color-scheme: dark) {
@theme {
--color-background: var(--color-neutral-900);
--color-foreground: var(--color-neutral-50);
--color-muted: var(--color-neutral-800);
--color-border: var(--color-neutral-700);
}
}
Usando os Tokens
<!-- Os tokens viram classes automaticamente -->
<div class="bg-background text-foreground">
<button class="bg-primary hover:bg-primary-hover text-white rounded-lg px-md py-sm">
Botão Primary
</button>
</div>
Componentes Base
Com os tokens definidos, vamos criar componentes reutilizáveis:
Button Component
// components/ui/button.tsx
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
// Base styles
'inline-flex items-center justify-center font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-white hover:bg-primary-hover shadow-sm',
secondary: 'bg-muted text-foreground hover:bg-muted/80',
outline: 'border border-border bg-transparent hover:bg-muted',
ghost: 'hover:bg-muted',
destructive: 'bg-error text-white hover:bg-error/90',
},
size: {
sm: 'h-8 px-3 text-sm rounded-md',
md: 'h-10 px-4 text-sm rounded-lg',
lg: 'h-12 px-6 text-base rounded-lg',
icon: 'h-10 w-10 rounded-lg',
},
},
defaultVariants: {
variant: 'default',
size: 'md',
},
}
);
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
isLoading?: boolean;
}
export function Button({
className,
variant,
size,
isLoading,
children,
...props
}: ButtonProps) {
return (
<button
className={cn(buttonVariants({ variant, size }), className)}
disabled={isLoading}
{...props}
>
{isLoading ? (
<svg className="animate-spin h-4 w-4 mr-2" viewBox="0 0 24 24">
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
fill="none"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
/>
</svg>
) : null}
{children}
</button>
);
}
Card Component
// components/ui/card.tsx
import { cn } from '@/lib/utils';
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
variant?: 'default' | 'bordered' | 'elevated';
}
export function Card({
className,
variant = 'default',
children,
...props
}: CardProps) {
return (
<div
className={cn(
'rounded-xl bg-background p-6',
{
'border border-border': variant === 'default' || variant === 'bordered',
'shadow-lg': variant === 'elevated',
},
className
)}
{...props}
>
{children}
</div>
);
}
export function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn('mb-4', className)} {...props} />;
}
export function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
return <h3 className={cn('text-xl font-semibold text-foreground', className)} {...props} />;
}
export function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn('text-muted-foreground', className)} {...props} />;
}
Container Queries (Novo!)
O Tailwind v4 traz suporte nativo a Container Queries:
<!-- Definir container -->
<div class="@container">
<!-- Estilos baseados no tamanho do container, não da viewport -->
<div class="@sm:flex @md:grid @md:grid-cols-2 @lg:grid-cols-3">
<div class="@sm:w-1/2 @md:w-full">
Card responsivo ao container
</div>
</div>
</div>
Exemplo Prático
// components/responsive-card.tsx
export function ResponsiveCard({ title, description, image }: Props) {
return (
<article className="@container">
<div className="flex flex-col @md:flex-row gap-4 p-4 rounded-xl border border-border">
{/* Imagem adapta ao container */}
<div className="@md:w-1/3">
<img
src={image}
alt={title}
className="w-full aspect-video @md:aspect-square object-cover rounded-lg"
/>
</div>
{/* Conteúdo */}
<div className="@md:w-2/3 flex flex-col justify-center">
<h3 className="text-lg @lg:text-xl font-semibold">{title}</h3>
<p className="text-muted-foreground @lg:text-lg mt-2">{description}</p>
</div>
</div>
</article>
);
}
Integrando com Figma
Exportando Tokens do Figma
{
"colors": {
"brand": {
"50": { "value": "#f0fdf4", "type": "color" },
"500": { "value": "#22c55e", "type": "color" },
"900": { "value": "#14532d", "type": "color" }
}
},
"spacing": {
"xs": { "value": "4px", "type": "spacing" },
"sm": { "value": "8px", "type": "spacing" },
"md": { "value": "16px", "type": "spacing" }
}
}
Script de Conversão
// scripts/tokens-to-css.js
const tokens = require('./figma-tokens.json');
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? `${parseInt(result[1], 16)} ${parseInt(result[2], 16)} ${parseInt(result[3], 16)}`
: null;
}
let css = '@theme {\n';
// Colors
Object.entries(tokens.colors).forEach(([name, shades]) => {
Object.entries(shades).forEach(([shade, { value }]) => {
css += ` --color-${name}-${shade}: ${hexToRgb(value)};\n`;
});
});
// Spacing
Object.entries(tokens.spacing).forEach(([name, { value }]) => {
css += ` --spacing-${name}: ${value};\n`;
});
css += '}';
console.log(css);
Dark Mode
Implementação com CSS
/* Suporte a preferência do sistema e classe manual */
@theme {
--color-background: 255 255 255;
--color-foreground: 23 23 23;
}
.dark {
--color-background: 23 23 23;
--color-foreground: 250 250 250;
}
@media (prefers-color-scheme: dark) {
:root:not(.light) {
--color-background: 23 23 23;
--color-foreground: 250 250 250;
}
}
Theme Toggle Component
// components/theme-toggle.tsx
'use client';
import { useTheme } from 'next-themes';
import { Button } from './ui/button';
import { FiSun, FiMoon } from 'react-icons/fi';
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
aria-label="Alternar tema"
>
<FiSun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<FiMoon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
</Button>
);
}
Documentação do Design System
Storybook Setup
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './button';
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['default', 'secondary', 'outline', 'ghost', 'destructive'],
},
size: {
control: 'select',
options: ['sm', 'md', 'lg', 'icon'],
},
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Default: Story = {
args: {
children: 'Button',
variant: 'default',
size: 'md',
},
};
export const AllVariants: Story = {
render: () => (
<div className="flex gap-4 flex-wrap">
<Button variant="default">Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
</div>
),
};
Checklist de Design System
Fundações
- Definir paleta de cores (brand, neutral, semantic)
- Definir escala de espaçamento
- Definir tipografia (fonts, sizes, weights)
- Definir border radius e shadows
- Definir breakpoints e container queries
Componentes
- Button (variantes e tamanhos)
- Card (header, content, footer)
- Input e Form fields
- Modal/Dialog
- Navigation components
- Feedback components (Toast, Alert)
Infraestrutura
- Setup Storybook para documentação
- Testes de acessibilidade
- Testes visuais (Chromatic)
- CI/CD para publicação
Conclusão
O Tailwind CSS v4 traz uma nova era para construção de Design Systems. A configuração via CSS com @theme simplifica a integração com ferramentas de design e torna o sistema mais fácil de manter.
Recursos Adicionais
Documentação:
Ferramentas:
Este guia será atualizado conforme novas features do Tailwind v4 forem lançadas.

