Skip to content

Shadcn Form Controls

Use shadcn/ui primitives for all user-facing React form controls in apps and shared UI surfaces.

Core Contract

  • Do not render user-facing native <input>, <select>, or <textarea> directly in feature code.
  • Use local shadcn primitives instead:
    • text-like fields: Input
    • multiline fields: Textarea
    • option menus: Select
    • booleans: Checkbox or Switch
    • single-choice sets: RadioGroup / RadioGroupItem
    • small mutually exclusive toggles: ToggleGroup when the app has that primitive
  • Do not build custom role="radiogroup", role="combobox", or select-like widgets from buttons/divs when a shadcn/Radix primitive exists.
  • Preserve existing form behavior when migrating, especially React Hook Form bindings, URL-driven GET filters, server-action payloads, data-testid hooks, and i18n labels.
  • For search-bar placeholder and aria-label semantics, follow 058-search-input-ux-policy.mdc.

Labels And Accessibility

  • Every visible control must have a visible label and a programmatic association.
  • Exception: search/filter bars may use placeholder + aria-label without a visible label, per 058-search-input-ux-policy.mdc.
  • For shadcn Select, prefer Label/label plus SelectTrigger with id and aria-labelledby when needed.
  • Validation and helper text must remain wired with aria-invalid and aria-describedby.
  • Radio groups must expose a group label via visible text, aria-label, or aria-labelledby.

Allowed Exceptions

  • Native fields inside src/components/ui/* shadcn primitive implementations are expected.
  • Hidden or programmatic inputs are allowed when they are plumbing only, such as file-picker triggers or hidden fields preserving form/query behavior.
  • Input type="color" is allowed for color wells; do not use a raw native <input type="color"> in feature code.
  • Native datalist is allowed only as an intentional lightweight suggestion layer on top of shadcn Input; document the reason near the datalist.
  • Shared package plain-HTML fallback adapters may use native elements for host-agnostic rendering, but production app adapters should map controls to shadcn primitives.

Migration Checklist

  • Add missing shadcn primitives to the app before importing them.
  • For React Hook Form, use Controller when replacing native selects or composite primitives.
  • For URL-driven filters, keep query serialization stable and preserve pagination/search params.
  • Update focused component tests for value changes, submit/query behavior, keyboard access, and error states.
  • Run full repo validation after changes: npm run check.