# Forms
Form UI must use shared primitives so controls match buttons, cards, and focus rings.
## Building blocks
| Piece | Import |
| --- | --- |
| Input | `@/components/ui/input` |
| Textarea | `@/components/ui/textarea` |
| Label | `@/components/ui/label` |
| Checkbox / Radio / Switch / Select | `@/components/ui/*` |
| Field layout | `@/components/ui/field` (`Field`, `FieldGroup`, `FieldSet`, …) |
| Form (RHF) | `@/components/ui/form` + `react-hook-form` + zod resolvers |
| Button submit | `@/components/ui/button` |
## Input spec
From `components/ui/input.tsx`:
| Property | Value |
| --- | --- |
| Height | `h-9` (36px) default; align to buttons with `h-[var(--btn-height-lg)]` when paired with `size="lg"` submit |
| Radius | `rounded-md` (`--radius-md`) |
| Border | `border-input` |
| Background | `bg-background`; dark: `dark:bg-surface/60` |
| Text | `text-base md:text-sm` (16px on mobile avoids iOS zoom) |
| Padding | `px-3 py-1` |
| Shadow | `shadow-xs` |
| Transition | `duration-200` on color/border/shadow |
| Focus | `border-primary` + `ring-[3px] ring-primary/25` |
| Disabled | `opacity-50`, no pointer |
| Invalid | `aria-invalid` → destructive border/ring |
## Field layout
- Vertical (default): label above control, `gap-3` within field; groups use `gap-7`.
- Label → control spacing: prefer `typeSpacing.labelToControl` (`gap-1.5`) or Field defaults.
- Checkbox/radio groups: tighter `gap-3`.
- Errors: use `text-destructive` and wire `aria-invalid` on the control.
```tsx
import { Field, FieldLabel, FieldDescription, FieldError } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";