Ir ao conteúdo
SEED engenhariaDesign System

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 0d0a374f

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).

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.json valem (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 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 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#

  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 ERP 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 — detecta ~40% dos issues
  • 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 (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 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