Pular para o conteúdo

4. Stack · versão 2.0.0

Design System e tokens

manifesto/03-stack/06-design-system.md

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 o typecheck — a corrente de tipagem vai do token ao componente, sem any e 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 varUsoOrigem
Primary--primary / --primary-foregroundação primária, marcashadcn
Secondary--secondary / --secondary-foregroundação secundáriashadcn
Destructive--destructive / --destructive-foregrounderro, exclusãoshadcn — é o “Error” do design
Texto e neutros--foreground · --muted-foreground · --bordertexto e neutrosshadcn
Accent (tonal)--accent / --accent-foregroundhover, seleção, botão tonalshadcn
Success (extensão)--success / --success-foregroundsucesso, confirmaçãoextensão — não existe no shadcn
Warning (extensão)--warning / --warning-foregroundatenção, alertaextensão — não existe no shadcn
Background / Card / Popover--background, --card, --popover (+ -foreground)superfíciesshadcn
Border / Input / Ring--border, --input, --ringbordas, campos, anel de focoshadcn

Regras de mapeamento (obrigatórias):

  • “Error” (design) → --destructive (nunca um token --error solto).
  • Os neutros de texto do design se distribuem em --foreground, --muted-foreground e --border.
  • success e warning são extensões: entram como CSS vars próprias e como variantes no cva dos 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

TokenRegra
EspaçamentoEscala do Tailwind (base 4: 1 = 4px, 2 = 8px, 4 = 16px…), consumida pelos utilitários (p-*, gap-*). Sem pixels avulsos.
RaioUm --radius base; os derivados saem dele (lg = radius, md = radius - 2px, sm = radius - 4px).
SombrasNí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.colors apontando 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.
  • success e warning nunca como cor solta — sempre CSS var + variante no cva.