Quando usar: input/select/textarea/switch com label, ajuda e/ou erro/disabled de validação.
Como: envolver em Field, FieldLabel htmlFor, FieldDescription para ajuda, FieldError para erro (agrupar em FieldGroup; seções em FieldSet+FieldLegend). Estado: aria-invalid/disabled no controle e data-invalid/data-disabled no Field pai simultaneamente.
Trade-offs: esquecer um dos dois atributos de estado deixa o estilo inconsistente com a funcionalidade.
Quando usar: seleção de plano/preferência onde o card inteiro deve ser clicável.
Como: FieldLabel envolvendo o Field inteiro, controle (RadioGroupItem/Switch) como filho direto, orientation="horizontal".
Quando usar: <a> que precisa parecer Button (navegação, não ação).
Como: buttonVariants({variant, size}) como className em <a> puro, nunca Button render={<a/>}.
Trade-offs: Button real força role="button", quebrando semântica de link.
Quando usar: ícone, texto fixo, botão ou contador dentro da borda visual do Input/Textarea.
Como: InputGroupInput/InputGroupTextarea dentro de InputGroup, com InputGroupAddon align="inline-start|inline-end|block-start|block-end" depois do controle no DOM.
Trade-offs: mais boilerplate que um Input simples.
Quando usar: instalar shadcn/ui em Next/Vite/Astro/React Router/TanStack Start.
Como: (1) shadcn/create gerando init --preset [CODE] --template <fw>, (2) init -t <fw> scaffoldando projeto novo, (3) projeto existente: Tailwind+alias manual, depois shadcn init.
Trade-offs: (1)/(2) só para projeto novo; (3) único caminho para projeto existente.
Quando usar: cor semântica nova além do tema default (esquecer .dark deixa o token quebrado no modo escuro).
Como: definir em :root E .dark, expor em @theme inline como --color-<nome>: var(--<nome>).
Quando usar: seletor de data compacto (não existe <DatePicker> pronto); evitar offset de um dia por timezone.
Como: Popover > PopoverTrigger (render Button) > PopoverContent > Calendar; date-fns pra formatar; data-empty={!date} no trigger; setOpen(false) no onSelect pra fechar; detectar timezone com Intl.DateTimeFormat().resolvedOptions().timeZone dentro de useEffect (nunca no render) e passar via prop timeZone.
Trade-offs: mais verboso que componente único; detectar timezone no render quebra hidratação SSR.
Quando usar: seleção múltipla com tags removíveis, ou combobox que deve se comportar como Select fechado.
Como: multi-select troca ComboboxInput por ComboboxChips > ComboboxValue + ComboboxChipsInput, value array, multiple na raiz. Select-popup usa ComboboxTrigger render={<Button/>} com ComboboxValue, e ComboboxInput showTrigger={false} dentro de ComboboxContent.
Trade-offs: multi-select muda a estrutura inteira; select-popup perde busca sempre visível em troca de visual compacto.
Quando usar: formulário/painel com UX diferente por tamanho de tela.
Como: useMediaQuery("(min-width: 768px)") decide Dialog vs Drawer; mesmo componente de form e state open/onOpenChange compartilhados.
Trade-offs: duplica árvore de trigger/header, evita duplicar lógica do form.
Quando usar: Button disabled precisa explicar o motivo via hover.
Como: Button disabled não dispara pointer events; envolver com <span className="inline-block w-fit"> via TooltipTrigger render={<span/>}. Workaround padrão documentado.
Quando usar: ação irreversível que exige resposta explícita. Não fecha ao clicar fora, reservar pra decisões que precisam ser forçadas.
Como: AlertDialogTrigger render={<Button variant="destructive">}, AlertDialogContent size="sm", AlertDialogAction variant="destructive".
Quando usar: menu de ações (API idêntica nos três) ou busca rápida via teclado (Cmd+K).
Como: menus: *Group+*Label seções; *CheckboxItem toggles; *RadioGroup+*RadioItem exclusiva; *Sub+*SubTrigger+*SubContent submenu; variant="destructive" ação perigosa (Dropdown exige *Portal ao redor de SubContent, Context Menu não). Command Palette: Command (base cmdk, não Base UI) dentro de CommandDialog open/onOpenChange controlado; CommandGroup heading, CommandEmpty.
Quando usar: ação reversível (deletar, arquivar) sem bloquear a UI. Fluxo assíncrono longo prefira toast.promise.
Como: capturar id de toast.add({actionProps: {children: "Undo", onClick(){toast.close(id)}}}).
Quando usar: data table só com os recursos necessários (mais setup que v8, mas bundle menor).
Como: tableFeatures({...}) centralizado, compartilhado entre columns.tsx/data-table.tsx/page.tsx.
Quando usar: gráficos consistentes entre light/dark (Recharts v3 não usa mais hsl(var(...)), código antigo precisa ajuste).
Como: --chart-1...--chart-5 em :root/.dark; chartConfig referencia "var(--chart-1)"; fill="var(--color-KEY)" (chave do config vira --color-KEY dentro do ChartContainer).
Quando usar: conectar componente shadcn/ui controlado ao RHF (verboso por campo, mas controle total de markup).
Como: <Controller render={({field, fieldState}) => <Field data-invalid={fieldState.invalid}><Control {...field} aria-invalid={fieldState.invalid}/><FieldError errors={[fieldState.error]}/></Field>}/>. Select/Checkbox/RadioGroup/Switch: mapear field.value→value/checked, field.onChange→onValueChange/onCheckedChange.
Quando usar: lista de itens adicionável/removível em formulário (nenhuma lib compartilha sintaxe, migrar entre elas reescreve a seção).
Como: RHF useFieldArray→fields/append/remove; TanStack Form mode="array" no form.Field→pushValue/removeValue; Formisch <FieldArray>+funções insert/remove top-level.
Quando usar: toggle manual light/dark/system.
Como: persistir preferência (localStorage Vite, cookie via remix-themes, next-themes no Next, script inline+MutationObserver no Astro), aplicar/remover classe dark no <html>.
Trade-offs: estratégia difere por framework (SSR/hidratação); Next/Remix evitam flash via mecanismo próprio.
Quando usar: suporte a árabe/hebraico/persa.
Como: shadcn create --template <fw> --rtl (rtl:true), <DirectionProvider direction="rtl">, dir="rtl"/lang no <html>, fonte Noto.
Trade-offs: automático só em estilos novos; Calendar/Pagination/Sidebar exigem migração manual; projeto existente roda shadcn migrate rtl.
Quando usar: formulário Next.js com validação client+server sem RHF.
Como: schema Zod + FormState → server action "use server" com safeParse retornando {values, errors, success} → client useActionState(action, initialState) ligando pending/data-invalid/aria-invalid.
Trade-offs: roundtrip ao servidor a cada submit, mas elimina duplicação de schema.
Quando usar: customizar item de registry externo sem fork; ou fazer o registry próprio funcionar com aliases diferentes entre consumidores.
Como: override cria item próprio com registryDependencies: ["@vendor/button"], sobrepondo cssVars/files com mesmo target (último vence). Portabilidade: files[].target usa @components/, @ui/, @lib/, @hooks/ em vez de caminho hardcoded.
Trade-offs: override só vale pra mudanças pequenas (cor/token); sem placeholder pra utils, e paths no meio da string não são resolvidos.
Quando usar: URL raiz precisa servir landing/docs a humanos e JSON ao CLI/MCP sem subpath; e/ou registry não deve ser público.
Como: negociação por headers Accept: application/vnd.shadcn.v1+json/User-Agent: shadcn (JSON se presentes, senão HTML, com Vary correto); autenticação via components.json#registries.@nome.headers.Authorization = "Bearer ${TOKEN}", servidor valida e retorna 401/403 customizado.
Trade-offs: Open in v0 só aceita auth via query param ?token=, não headers.