4. Stack · versão 2.0.0
Design System e tokens
Esta página fixa as regras do Design System (o quê): o contrato de design tokens que todo projeto com interface segue. Os padrões de construção (o como) estão em Design (UI/UX), e o esqueleto da documentação e o checklist de entrega de uma tela, no modelo de Design System.
Princípio: o token é a fonte única
Assim como o Zod é a fonte única da API (validação + OpenAPI + testes — ver OpenAPI e Scalar), o design token é a fonte única do visual. Os tokens declarados no CSS do tema, versionados no repositório, são a origem de:
- as CSS variables (
globals.css), - o tema do Tailwind (
@theme inline), - as variantes de componente (
cva), - o espelho no Figma, quando o projeto tem um.
Documentação e implementação não divergem porque saem do mesmo lugar. Nenhuma cor, tamanho ou
sombra é escrita direto na UI (#3b82f6, 12px) — sempre via token (bg-primary, gap-2).
Um fato, um lugar (princípio 4). O token é fato do código: o repositório é canônico. Uma ferramenta de design (Figma ou outra) é espelho: serve para capturar os tokens no início e para acompanhar o visual, mas uma mudança feita lá só vira verdade quando é trazida ao repositório por uma ADR.
Tipagem total (princípio 6). As variantes (
cva) são tipadas a partir das opções que existem. Trocar o nome de uma variante quebra otypecheck— a corrente de tipagem vai do token ao componente, semanye sem string solta.
Vocabulário de cores (semântico)
O Design System usa papéis semânticos, não cores cruas. As rampas tonais (paletas) são matéria-prima; o que a UI consome são os tokens semânticos abaixo. A convenção segue o shadcn/ui, estendida com os papéis que ele não tem.
| Papel (token) | CSS var | Uso | Origem |
|---|---|---|---|
| Primary | --primary / --primary-foreground | ação primária, marca | shadcn |
| Secondary | --secondary / --secondary-foreground | ação secundária | shadcn |
| Destructive | --destructive / --destructive-foreground | erro, exclusão | shadcn — é o “Error” do design |
| Texto e neutros | --foreground · --muted-foreground · --border | texto e neutros | shadcn |
| Accent (tonal) | --accent / --accent-foreground | hover, seleção, botão tonal | shadcn |
| Success (extensão) | --success / --success-foreground | sucesso, confirmação | extensão — não existe no shadcn |
| Warning (extensão) | --warning / --warning-foreground | atenção, alerta | extensão — não existe no shadcn |
| Background / Card / Popover | --background, --card, --popover (+ -foreground) | superfícies | shadcn |
| Border / Input / Ring | --border, --input, --ring | bordas, campos, anel de foco | shadcn |
Regras de mapeamento (obrigatórias):
- “Error” (design) →
--destructive(nunca um token--errorsolto). - Os neutros de texto do design se distribuem em
--foreground,--muted-foregrounde--border. successewarningsão extensões: entram como CSS vars próprias e como variantes nocvados componentes que as usam (Badge, Button, aviso).- Rampas tonais não são expostas direto na UI — servem para derivar os tokens semânticos.
Modos claro e escuro
O padrão é oferecer os dois modos, definidos por token: :root (claro) e .dark (escuro) no
globals.css. Todo token de cor tem valor em cada modo que o projeto oferece. Um projeto que
decide ter um modo só (por identidade visual, por exemplo) registra isso numa ADR e não publica o
modo que não existe.
Espaçamento, raio e sombras
| Token | Regra |
|---|---|
| Espaçamento | Escala do Tailwind (base 4: 1 = 4px, 2 = 8px, 4 = 16px…), consumida pelos utilitários (p-*, gap-*). Sem pixels avulsos. |
| Raio | Um --radius base; os derivados saem dele (lg = radius, md = radius - 2px, sm = radius - 4px). |
| Sombras | Níveis nomeados E-1 … E-4 → --shadow-1 … --shadow-4. E-1 cartões em repouso · E-2 cartões elevados · E-3 menus e painéis · E-4 diálogos. |
Tipografia
Estilos de texto são tokens: 1 estilo do design = 1 token (heading-1, body, label,
caption…), cada um com tamanho, altura de linha, peso e espaçamento entre letras. Sem tamanhos de
fonte avulsos na UI. As fontes carregam pelo next/font e entram no tema como variáveis.
Ícones
Biblioteca padrão: lucide-react (a do shadcn/ui). Uma família diferente (ícones próprios, pixel
art) é decisão de identidade e entra por ADR, com um componente único para desenhá-los. Todo ícone
sem texto recebe aria-label; ícone decorativo, aria-hidden.
Esqueleto de implementação (Tailwind v4)
Os valores são preenchidos na ADR que funda o Design System. O shadcn/ui atual usa
oklch(...); escolha um formato de cor por projeto.
@import "tailwindcss";
@custom-variant dark (&:is(.dark *));
:root { --radius: 0.5rem; /* superfícies */ --background: oklch(…); --foreground: oklch(…); --card: oklch(…); --card-foreground: oklch(…); --popover: oklch(…); --popover-foreground: oklch(…); /* marca */ --primary: oklch(…); --primary-foreground: oklch(…); --secondary: oklch(…); --secondary-foreground: oklch(…); --muted: oklch(…); --muted-foreground: oklch(…); --accent: oklch(…); --accent-foreground: oklch(…); /* botão tonal */ /* situação */ --destructive: oklch(…); --destructive-foreground: oklch(…); --success: oklch(…); --success-foreground: oklch(…); /* extensão */ --warning: oklch(…); --warning-foreground: oklch(…); /* extensão */ /* contorno */ --border: oklch(…); --input: oklch(…); --ring: oklch(…); /* sombras */ --shadow-1: …; --shadow-2: …; --shadow-3: …; --shadow-4: …;}
.dark { --background: oklch(…); --foreground: oklch(…); /* … os mesmos tokens, com os valores do modo escuro … */}
@theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); --color-card-foreground: var(--card-foreground); --color-popover: var(--popover); --color-popover-foreground: var(--popover-foreground); --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); --color-secondary: var(--secondary); --color-secondary-foreground: var(--secondary-foreground); --color-muted: var(--muted); --color-muted-foreground: var(--muted-foreground); --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-destructive: var(--destructive); --color-destructive-foreground: var(--destructive-foreground); --color-success: var(--success); --color-success-foreground: var(--success-foreground); --color-warning: var(--warning); --color-warning-foreground: var(--warning-foreground); --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); --radius-lg: var(--radius); --radius-md: calc(var(--radius) - 2px); --radius-sm: calc(var(--radius) - 4px); --shadow-elevation-1: var(--shadow-1); --shadow-elevation-2: var(--shadow-2); --shadow-elevation-3: var(--shadow-3); --shadow-elevation-4: var(--shadow-4); --font-sans: var(--font-geist-sans); /* a variável do next/font (o nome é da ADR); nunca var(--font-sans) */ --text-heading-1: …; --text-heading-1--line-height: …; --text-body: …; --text-label: …; --text-caption: …;}Projetos em Tailwind v3 declaram o mesmo contrato no
tailwind.config(theme.extend.colorsapontando para as CSS vars). As regras acima valem para os dois — muda só a sintaxe da ligação.
Proibições duras
- Sem cor, tamanho ou sombra escritos direto na UI. Tudo via token.
- Sem token de cor sem valor em cada modo que o projeto oferece.
successewarningnunca como cor solta — sempre CSS var + variante nocva.