Boas práticas
14. Código React com o design system — boas práticas
seed-praticas.md v1.13 · §14seção 16 de 2514-codigo-react-com-o-design-system-boas-praticas.md · MD5 4736c7bbOrigem:
seed-ds-ui/references/melhores-praticas-react-ds.md(skill de 2026-05-13, 15871 bytes, MD56bcae4ba45d3ad46db2e1f9d0cd3c72a; cópia preservada em09-pesquisa/skills-v1-2026-05/referencias/melhores-praticas-react-ds.md) · Importado em 2026-09-06 sem edição — só os títulos foram rebaixados dois níveis (código intacto).Revisão: conteúdo revisado em 2026-09-08 · ciclo de 6 meses (V-B3 = A) · vence em 2027-03-08
Estado: ✅ auditada em 2026-09-08 (F9.8, onda 4). Os cinco princípios e o código ficaram — são bons e rodam. Corrigidos: a fonte dos tokens (a seção tratava o apelido do shadcn como origem, quando ele é camada derivada), o caminho de consumo (hoje existe registry publicado, que em maio não existia), a plataforma do SIGA — que abriu uma divergência entre dois canônicos, virou a pendência
PB-P6— e dois números de mercado sem fonte. Ver 14.0.
Melhores práticas — React design system + cva + WCAG 2.2 AA#
Reference exclusiva da skill seed-ds-ui. Síntese de práticas estabelecidas no mercado (autoridades + dados de benchmark 2024-2026) aplicáveis ao código React/TS do SIGA via tokens.
Consultar sempre antes de produzir componente novo, refatorar componente existente, ou montar feature integrada — princípios + autoridades.
14.0 O que a auditoria de 2026-09-08 corrigiu#
Valem as etiquetas da §1.0.
⚠ O que esta seção não consegue provar: que um padrão de código faz o time entregar mais rápido ou o produto ficar melhor. Os dois números que ela trazia nessa direção saíram. O que sustenta as regras aqui é conformidade com o design system (verificável por grep) e conformidade com a WCAG (verificável por ferramenta), não desempenho.
⛔ 1. A fonte do token não é o apelido do shadcn. A seção dizia: "todo bg-/text-/border- aponta para token SEED (18 canônicos em paleta.md) ou alias shadcn (--primary…)", tratando os dois como equivalentes. Não são, e a diferença importa:
- A fonte são as variáveis
--seed-*— hoje 575 delas (primitivos e semânticos, claro e escuro), instaláveis pelo itemtokensdo registry. - Os 32 apelidos do shadcn (
--primary,--secondary,--sidebar-*,--chart-*) são camada derivada: o itemthemedo registry os aponta para os semânticos do DS. Quem edita apelido está editando a tradução, não o sistema. - A regra canônica é o §44 do
seed-componentes.md(guia de integração Tailwind/shadcn, GI1–GI5), que é o documento de consumo — esta seção é orientação, aquele é canon.
E paleta.md não existe mais como fonte: é material de maio/2026 preservado em 09-pesquisa/. Os "18 tokens" são de outro mundo.
Como consumir, hoje: instalar do registry publicado em https://ds.seed.eng.br/r/ — tokens (as variáveis --seed-*), theme (os apelidos apontando para elas) e fontes (Montserrat e JetBrains Mono, hospedadas na casa). Em maio de 2026 esse caminho não existia; hoje existe, e é ele.
⚠ 2. A plataforma do SIGA: dois canônicos discordam — pendência PB-P6. A seção diz "o stack do ERP SEED via Lovable". O seed-ds-roadmap.md traz um supersede explícito na v2.6: "o site institucional é Next.js na VM e o ERP é Vite/TanStack". Já o sobreaseed.md, na linha de 2026 da trajetória, registra "construção do ERP próprio em andamento (plataforma Lovable)". As duas coisas podem ser verdade em momentos diferentes, mas como estão escritas, se contradizem — e esta base não resolve divergência entre canônicos em silêncio. Fica a pendência. Enquanto isso, o que é seguro dizer e não depende da decisão: React + TypeScript + Tailwind + shadcn/ui + Supabase, consumindo o DS pelo registry.
⛔ 3. Dois números de mercado, sem fonte. "shadcn/ui é o padrão de fato em 2026, supera MUI e Chakra (DesignRevision 2026)" e "formulário de login completo em ~8 minutos com shadcn contra 45 do zero (Atlas, dev.to 2026)" — blog de fornecedor e post pessoal, sem amostra, sem método, sem período. Removidos. A escolha do shadcn no SEED não precisa deles: ela se justifica por posse do código gerado (o componente entra no repositório e é auditável), pela acessibilidade que vem do Radix e pela ausência de runtime próprio.
⚠ 4. Uma imprecisão de rótulo na WCAG, e uma contradição com a §12. Os seis critérios listados abaixo estão certos, mas dois deles são nível A, não AA (3.2.6 Consistent Help e 3.3.7 Redundant Entry). O correto: a WCAG 2.2 acrescenta nove critérios — dois em A, quatro em AA e três em AAA — e torna obsoleto o 4.1.1. E esta seção estava certa sobre o alvo de toque (2.5.8 pede 24 × 24 px em AA; 44 × 44 é o 2.5.5, de AAA) enquanto a §12 dizia que 44 × 44 é exigência de AA — as duas seções se contradiziam, e a §12 foi corrigida.
⚠ 5. ~40 % das violações detectadas por ferramenta automática: número que circula em várias versões (30 %, 40 %, 57 %, conforme o estudo e o conjunto de páginas). [não conferido]. O que fica de pé sem número: ferramenta automática não substitui teste manual nem teste com pessoa — e essa é a única parte que muda a conduta.
Autoridades de referência#
Quando o usuário pedir "no estilo de" ou pegar dúvida sobre padrão, reconhecer estas fontes:
| Fonte | Autoridade | Aplicável a |
|---|---|---|
| shadcn/ui (shadcn.com) | Padrão de fato do mercado React+Tailwind em 2026 — não é library, é gerador de código | Component generation pattern (npx shadcn add), cva variants, cn() utility |
| Radix UI (radix-ui.com) | Headless primitives com a11y completa — base do shadcn | Comportamento + acessibilidade ARIA WAI |
| Joe Bell (cva.style) | Criador do class-variance-authority | API canônica de variantes |
| W3C WAI (w3.org/WAI) | Working Group oficial WCAG | Standard de acessibilidade WCAG 2.2 (ISO/IEC 40500:2025) |
| Deque University (dequeuniversity.com) | Maior referência prática de a11y | Casos, testes, axe-core |
| Vercel Academy (vercel.com/academy) | Tutorial canônico shadcn/ui anatomy | Patterns React Server Components + shadcn |
| Infinum Frontend Handbook | Best practices estabelecidas | CVA patterns, testing (Storybook + Playwright + jest-axe) |
Aplicação SEED: o caminho comum do ecossistema é React + TypeScript + Tailwind + shadcn/ui + Supabase, consumindo o design system pelo registry publicado. ⚠ Sobre a plataforma em que o SIGA é construído, dois canônicos divergem — ver 14.0, item 2, e a pendência PB-P6. As práticas destas autoridades se aplicam de qualquer forma, porque são sobre React e acessibilidade, não sobre a plataforma.
Dados de benchmark (estado da arte 2026)#
Stack e velocidade#
⚠ Dois números que estavam aqui saíram por falta de fonte (14.0, item 3). O que fica é o que se pode afirmar sem medição de terceiro:
- A escolha do shadcn/ui no SEED se justifica pela posse do código: o componente é gerado dentro do repositório, fica auditável e versionado com o resto — diferente de biblioteca instalada, que muda por baixo.
- Acessibilidade herdada do Radix, que é a base do shadcn: comportamento de teclado, foco e ARIA já resolvidos por quem faz isso em tempo integral.
- Sem runtime próprio de estilo — o custo é de build, não de execução.
- TanStack Table para tabela com ordenação, filtro e virtualização: o shadcn dá a casca, o TanStack dá a lógica de dados.
Acessibilidade#
- WCAG 2.2 é ISO/IEC 40500:2025 (aprovação outubro 2025) — procurement de RFPs públicos e enterprise já exige 2.2 desde então
- A WCAG 2.2 acrescenta nove critérios — dois em nível A, quatro em AA e três em AAA — e torna obsoleto o 4.1.1 Parsing. (⚠ até a v0.6 esta linha dizia "6 em AA"; dois dos seis são nível A — ver 14.0, item 4.)
- Ferramenta automática não substitui teste manual nem teste com pessoa. O percentual de cobertura que estava aqui saiu: circula em versões diferentes conforme o estudo, e a conduta não depende dele.
Os 5 princípios duros do DS SEED (não negociáveis)#
1. Token-only styling — zero hardcoded, zero Tailwind padrão#
Regra: todo bg-/text-/border-/ring- aponta para uma variável --seed-* (a fonte, 575 delas, instaladas pelo item tokens do registry) ou para um dos 32 apelidos do shadcn (--primary, --secondary…), que são a camada derivada instalada pelo item theme e apontam para os semânticos do DS. A regra canônica é o §44 do seed-componentes.md. ⚠ Até a v0.6 esta linha dizia "18 canônicos em paleta.md" — arquivo de maio/2026, que não é fonte (14.0, item 1).
Proibido:
bg-[#11B0A0]— hardcoded hexbg-blue-500— cor Tailwind padrão (não existe no DS SEED)bg-cyan-600— idembg-purple-500— idem- Qualquer valor arbitrário
bg-[...]
Permitido:
bg-primary,bg-secondary,bg-accent,bg-muted,bg-card,bg-background— aliases shadcn (apontam pra SEED)bg-turquesa,bg-verde,bg-amarelo— tokens SEED diretos (quando o nome semântico não couber)
Por que importa: uma única cor Tailwind padrão importada num componente contamina o resto do codebase via copy-paste. Drift começa pequeno e vira incontrolável. Detectar via grep CI em bg-blue- etc é trivial.
2. Headless logic via Radix UI#
Regra: acessibilidade, keyboard navigation, focus trap, ARIA — tudo via Radix (já em shadcn). Nunca recriar.
Implicação prática:
<Dialog>shadcn vem com Esc-to-close + focus trap + aria-describedby — usar<DropdownMenu>vem com arrow keys + focus management — usar<Tabs>vem com aria-selected + arrow keys + Home/End — usar<Form>shadcn (envolve react-hook-form) vem com aria-invalid + aria-describedby + label htmlFor — usar
Não recriar: <div role="dialog"> com useEffect controlando Esc — está reinventando o que o Radix já fez melhor.
3. Variants via cva (class-variance-authority)#
Regra: quando componente tem variantes visuais, declarar via cva(). Não usar template literals condicionais (bg-${variant === 'primary' ? 'primary' : 'secondary'}).
Padrão canônico (anatomia de Vercel Academy + shadcn handbook):
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const componentVariants = cva(
// Base classes — sempre aplicadas, só tokens
"inline-flex items-center justify-center rounded-md transition-colors",
{
variants: {
variant: {
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90",
},
size: {
sm: "h-8 px-3 text-xs",
md: "h-10 px-4 text-sm",
lg: "h-11 px-8 text-base",
},
},
compoundVariants: [
// Combinações específicas (ex: primary + lg = ainda mais bold)
{ variant: "primary", size: "lg", class: "font-semibold" },
],
defaultVariants: {
variant: "primary",
size: "md",
},
}
);
export interface ComponentProps
extends React.HTMLAttributes<HTMLElement>,
VariantProps<typeof componentVariants> {}
Sub-regra (shadcn handbook 2026): cva deve ser usada com parcimônia. Não todo componente precisa de variants. Pergunte: "existem 2+ visuais legítimos desse componente?" Se não, sem cva.
4. cn() — merge inteligente de classes#
Regra: sempre cn(componentVariants({ variant, size, className })) em vez de string concat manual.
cn() vem do shadcn (em lib/utils.ts) — combina clsx (lógica condicional) + tailwind-merge (resolve conflitos Tailwind como bg-primary bg-secondary ficando só bg-secondary).
Importante: o className do prop sempre vem por último — permite override pelo consumer. Isso é o que faz a API funcionar:
<Button variant="primary" className="w-full">Click</Button>
// resultado: bg-primary ... w-full (override aplicado)
5. TypeScript strict — zero any#
Regra: TS strict mode. Props sempre tipadas. Variants tipadas via VariantProps<typeof cva>. Sem any em nenhum lugar.
// CERTO
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
// ERRADO
function Button(props: any) { ... }
VariantProps<typeof componentVariants> extrai automaticamente os tipos das variantes — single source of truth.
WCAG 2.2 AA — 9 novos critérios + o que isso muda no SEED#
WCAG 2.2 (publicado 2023, ISO/IEC 40500:2025) é o standard atual. Procurement enterprise já exige 2.2. Mudanças relevantes para o SIGA:
Os seis critérios novos que afetam o SEED (dois em A, quatro em AA)#
| Critério | O que exige | Aplicação SEED |
|---|---|---|
| 2.4.11 Focus Not Obscured (Minimum) | Elemento focado não pode estar 100% escondido atrás de sticky headers, banners, modals | Verificar sticky top bar + qualquer item da sidebar focado — não pode sumir |
| 2.5.7 Dragging Movements | Toda interação drag precisa alternativa single-pointer (botão, atalho teclado) | Drag-and-drop pra reordenar listas → sempre adicionar botões ↑/↓ |
| 2.5.8 Target Size (Minimum) | Targets ≥ 24×24 CSS px, ou espaçamento adequado entre alvos pequenos | shadcn default size="icon" é h-10 w-10 (40px) — funciona pra AA, mas mobile alvo primário 44×44 é melhor prática (2.5.5 AAA) |
| 3.2.6 Consistent Help | Mecanismos de ajuda repetidos em múltiplas páginas na mesma posição relativa | Ícone de ajuda sempre no mesmo canto em todas as páginas do SIGA |
| 3.3.7 Redundant Entry | Info já fornecida pelo usuário no mesmo processo não pode ser pedida de novo (autopreencher ou opção "usar dados anteriores") | Forms multi-step: dados de cliente já preenchidos no passo 1 não pedir de novo no passo 3 |
| 3.3.8 Accessible Authentication (Min) | Login não pode exigir teste cognitivo (puzzle, transcrição manual) sem alternativa | Sem CAPTCHA cognitivo. Permitir password manager + paste no campo senha + passkey. |
Critérios novos AAA (3, aspirational)#
- 2.4.12 Focus Not Obscured (Enhanced) — focused element 0% escondido
- 2.4.13 Focus Appearance — focus indicator com tamanho/contraste específicos
- 3.3.9 Accessible Authentication (Enhanced) — sem cognitive test em nenhuma exceção
Critério removido#
- 4.1.1 Parsing — obsoleto (browsers modernos lidam OK com HTML imperfeito)
O que isso implica no código SEED#
- Touch targets: Button
size="icon"no SEED deve ser h-11 w-11 (44×44px) em mobile primário — sobrescrever shadcn default h-10 w-10 - Focus visible: SEMPRE incluir
focus-visible:ring-2 focus-visible:ring-ringem todos os interactives. Cor--ring = --turquesa(alto contraste com branco) - Drag alternatives: se feature do SIGA usa drag (reordenar Kanban), adicionar
<button aria-label="Mover acima">↑</button>correspondente - Help consistency: ícone de ? sempre no mesmo canto do top bar, todas as páginas
- Form multi-step: se passos 1-N pedem dados, autopreencher do contexto se já tem
- Login: Supabase Auth nativo já cobre. Não adicionar CAPTCHA visual obrigatório.
Standard de testing (Infinum + Deque)#
- eslint-plugin-jsx-a11y no CI — pega uma parte dos problemas em tempo de escrita (o percentual que estava aqui saiu:
[não conferido]) - jest-axe ou
@axe-core/playwrightem testes — detecta + acessibilidade dinâmica - Manual + keyboard sweep obrigatório antes de deploy — automatizado não pega tudo
- Storybook + Playwright visual regression — variantes cva geram snapshots estáveis
Padrões arquiteturais do projeto SEED#
Estrutura de pastas (convenção shadcn)#
src/
├── components/
│ ├── ui/ # shadcn primitives — gerados via `npx shadcn add`
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── dialog.tsx
│ │ └── ...
│ └── seed/ # Componentes SEED customizados (extendem ou compõem shadcn)
│ ├── StatusBadge.tsx # variantes ATIVO/PROSPECT/INATIVO
│ ├── SegmentBadge.tsx # 6 variantes de segmento
│ ├── ClienteRow.tsx # row canônica da tabela de clientes
│ └── ...
├── pages/ # rotas (ClientesList, ClienteDetalhe, etc)
├── hooks/ # custom hooks
├── lib/ # utils, helpers
│ └── utils.ts # cn() helper
└── index.css # tokens SEED canônicos
Separação importante:
ui/= shadcn base, mexer com cuidado (recebe updates vianpx shadcn diff)seed/= onde a identidade SEED vive (compõe shadcn, customiza variants)
Forms — <Form> + react-hook-form + zod#
Padrão shadcn canônico:
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form";
const schema = z.object({
nome: z.string().min(3, "Nome muito curto"),
cnpj: z.string().regex(/^\d{2}\.\d{3}\.\d{3}\/\d{4}-\d{2}$/, "CNPJ inválido"),
});
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
});
A11y, validação e error messages — tudo automático via <FormMessage>.
Estado — useState/useReducer local + TanStack Query server#
- Local UI state (modal aberto/fechado, filtro selecionado):
useState - Complex local state (form com 20+ campos, wizard):
useReducer - Server state (dados do Supabase, lista de clientes):
@tanstack/react-query - Global app state (user logado, tema): React Context ou Zustand
Não usar Redux em projeto novo SEED — overhead desnecessário em 2026, TanStack Query + Context cobrem 95% dos casos.
Mobile-first responsive#
Default = mobile. Breakpoints adicionam:
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{/* mobile: 1 col · tablet: 2 · desktop: 3 */}
</div>
Anti-padrão: desktop-first com md:max-w- ou lg:hidden ocultando coisas no mobile.
Checklist pré-entrega de código SEED (15 itens)#
Antes de entregar componente/feature:
Drift de DS:
- [ ] Zero hardcoded colors (
bg-[#XXX],text-blue-500, etc) — grep mental - [ ] Zero cores Tailwind padrão (purple/blue/orange/pink/amber)
- [ ] Todos
bg-/text-/border-apontam pra tokens SEED ou aliases shadcn - [ ] Tipografia via
font-sans(Montserrat) ou explícito - [ ] Radius via token (
rounded-md,rounded-lg)
Acessibilidade WCAG 2.2 AA:
- [ ] Forms usam
<Form>shadcn (não recriar) - [ ] Dialogs usam
<Dialog>shadcn (não recriar) - [ ]
<label htmlFor>em todos inputs (ou<FormLabel>) - [ ]
altem todas imagens (decorativas:alt="") - [ ] Cor não é único indicador (status sempre com ícone OU texto além da cor)
- [ ] Focus visible ring nos interactives (
focus-visible:ring-2 focus-visible:ring-ring) - [ ] Touch targets ≥ 44×44px em Button
size="icon"mobile - [ ] Sem CAPTCHA cognitivo obrigatório (3.3.8)
- [ ] Drag-and-drop tem alternativa botão/atalho (2.5.7)
TypeScript + estrutura:
- [ ] Sem
anyno código - [ ] Props tipadas (interface ou type)
- [ ] Variants tipadas via
VariantProps<typeof cva> - [ ] Componente em
src/components/seed/(ouui/se primitive shadcn) - [ ] Mobile-first responsive
Microcopy:
- [ ] Tom SEED aplicado (sem jargão vetado)
- [ ] Grafia "SEED engenharia" correta em todos os elementos visíveis
Anti-padrões verificados em produção (NÃO repetir)#
bg-blue-500hardcoded porque "rapidinho" → contamina codebase- Recriar Dialog com
<div role="dialog">→ reinventa acessibilidade que Radix já tem - cva em todo componente (até nos sem variants) → overhead desnecessário (shadcn handbook 2026)
anyem props → perde safety, perde autocomplete- Template literals condicionais (
bg-${color}-500) → cva existe pra isso focus-visible:outline-nonesem replacement → usuário keyboard perdidosize="icon"h-10 w-10 em Button primário mobile → falha WCAG 2.2 AA touch target- CAPTCHA cognitivo sem alternativa em login → falha 3.3.8
- Sticky top bar escondendo focused element → falha 2.4.11
- Desktop-first com
md:hiddenocultando feature em mobile → mobile-first é padrão - Cor única como indicador ("verde = aprovado") → falha 1.4.1 Use of Color (acrescentar ícone ✓ ou texto)
- Importar cores Tailwind padrão (
bg-blue-,border-purple-) → quebra DS SEED
Composição com frontend-design oficial#
A skill frontend-design da Anthropic recomenda variar entre gerações pra evitar look genérico AI.
A skill seed-ds-ui sobrescreve essa recomendação no item estético: o DS SEED é fixo, converge. Mas concorda com frontend-design em:
- Production-grade quality
- Meticulous detail
- Accessibility
- Avoid AI slop (textos genéricos, ícones decorativos sem função, espaçamento aleatório)
Quando ambas ativam: frontend-design informa qualidade/profundidade, seed-ds-ui define paleta/tipografia/spacing/componentes.