# 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"; ایمیل برای پیگیری درخواست با شما تماس می‌گیریم. ایمیل معتبر نیست.
``` ## Validation & a11y 1. Associate every control with a visible `