Ir ao conteúdo
SEED engenharia

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 4736c7bb

Origem: seed-ds-ui/references/melhores-praticas-react-ds.md (skill de 2026-05-13, 15871 bytes, MD5 6bcae4ba45d3ad46db2e1f9d0cd3c72a; cópia preservada em 09-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 item tokens do registry.
  • Os 32 apelidos do shadcn (--primary, --secondary, --sidebar-*, --chart-*) são camada derivada: o item theme do 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 hex
  • bg-blue-500 — cor Tailwind padrão (não existe no DS SEED)
  • bg-cyan-600 — idem
  • bg-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#

  1. 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
  2. Focus visible: SEMPRE incluir focus-visible:ring-2 focus-visible:ring-ring em todos os interactives. Cor --ring = --turquesa (alto contraste com branco)
  3. Drag alternatives: se feature do SIGA usa drag (reordenar Kanban), adicionar <button aria-label="Mover acima">↑</button> correspondente
  4. Help consistency: ícone de ? sempre no mesmo canto do top bar, todas as páginas
  5. Form multi-step: se passos 1-N pedem dados, autopreencher do contexto se já tem
  6. 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/playwright em 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 via npx 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>)
  • [ ] alt em 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 any no código
  • [ ] Props tipadas (interface ou type)
  • [ ] Variants tipadas via VariantProps<typeof cva>
  • [ ] Componente em src/components/seed/ (ou ui/ 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)#

  1. bg-blue-500 hardcoded porque "rapidinho" → contamina codebase
  2. Recriar Dialog com <div role="dialog"> → reinventa acessibilidade que Radix já tem
  3. cva em todo componente (até nos sem variants) → overhead desnecessário (shadcn handbook 2026)
  4. any em props → perde safety, perde autocomplete
  5. Template literals condicionais (bg-${color}-500) → cva existe pra isso
  6. focus-visible:outline-none sem replacement → usuário keyboard perdido
  7. size="icon" h-10 w-10 em Button primário mobile → falha WCAG 2.2 AA touch target
  8. CAPTCHA cognitivo sem alternativa em login → falha 3.3.8
  9. Sticky top bar escondendo focused element → falha 2.4.11
  10. Desktop-first com md:hidden ocultando feature em mobile → mobile-first é padrão
  11. Cor única como indicador ("verde = aprovado") → falha 1.4.1 Use of Color (acrescentar ícone ✓ ou texto)
  12. 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.


Esc