---
fonte: 01-canonicos/seed-praticas.md
versao_da_fonte: v0.1
secao: 14
titulo: "Código React com o design system — boas práticas"
sequencia: 16 de 17
bytes_do_corpo: 16837
md5_do_corpo: 0d0a374f6db084bed88d767c5bfe3bd5
gerado_por: 06-validacao/geradores/gen-camada-ia.py
nota: fatia GERADA — o corpo abaixo é byte a byte o trecho do canônico; edite o canônico, nunca esta fatia. Canônico inteiro em https://ds.seed.eng.br/01-canonicos/seed-praticas.md
---
## 14. Código React com o design system — boas práticas

> **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):**

```tsx
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:

```tsx
<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.

```tsx
// 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:

```tsx
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:

```tsx
<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.

---

