Boas práticas
14. Código React com o design system — boas práticas
seed-praticas.md v0.1 · §14seção 16 de 1714-codigo-react-com-o-design-system-boas-praticas.md · MD5 0d0a374fOrigem:
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).Estado: ⚠ não auditado contra o DS v2. Contradições medidas — vale a regra de precedência do cabeçalho: 1 cor(es) em hex — só os tokens do
seed-tokens.jsonvalem (marca §3, §11) · cita o Lovable — o ERP é Vite/TanStack e o site é Next.js na VM (roadmap v2.6); o consumo de tokens é pelo registry@seed/*· usa apelidos do shadcn (--primary…) como se fossem a fonte — a fonte são as variáveis--seed-*(componentes §44 GI1/GI2; registry@seed/theme).
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 ERP SEED via tokens.
Consultar sempre antes de produzir componente novo, refatorar componente existente, ou montar feature integrada — princípios + autoridades.
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 stack do ERP SEED via Lovable (Vite + React + TS + Tailwind + shadcn + Supabase) é o happy path de todo o ecossistema. As práticas dessas autoridades aplicam diretamente, sem adaptação.
Dados de benchmark (estado da arte 2026)#
Stack e velocidade#
- shadcn/ui é o padrão de fato em 2026 para projetos React novos — supera MUI e Chakra pelas razões: ownership do código, zero runtime overhead, Tailwind-native (DesignRevision 2026)
- Login form completo (email + senha + validação + loading + a11y): ~8 minutos com shadcn + react-hook-form + Zod vs. 45 minutos do zero (Atlas, dev.to 2026)
- Server Components first é tendência consolidada — shadcn funciona bem porque maioria dos componentes é puramente presentational
- TanStack Table continua sendo o padrão para tabelas com sort/filter/virtualization complexa — shadcn fornece o Table base, TanStack faz data logic
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
- WCAG 2.2 adiciona 9 novos critérios (6 em AA), remove 1 (4.1.1 Parsing tornou-se obsoleto)
- Tools automatizados detectam ~40% das violações WCAG 2.2 — manual + user testing continuam essenciais
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 pra token SEED (18 canônicos em paleta.md) ou alias shadcn (--primary, --secondary etc, todos apontando pra SEED).
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 ERP SEED:
Critérios novos AA (6 que afetam o SEED)#
| 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 ERP |
| 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 ERP 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 — detecta ~40% dos issues
- 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 (Lovable + shadcn convenção)#
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.