Forms
Build controlled forms with validation, accessibility, and modern submission patterns including Server Actions.
Controlled Components
Controlled inputs bind value to React state and update via onChange handlers. React state is the single source of truth — the DOM reflects state, not the reverse. This enables instant validation, formatting, and conditional fields.
Each input type has specific event and value handling — checkboxes use `checked`, selects use `value` on the select element, file inputs use `FileList` from refs because paths are read-only.
- State drives input value
- onChange updates state
- Special handling for checkbox, file inputs
<input value={email} onChange={e => setEmail(e.target.value)} />Validation Strategies
Validate on blur, submit, or change depending on UX requirements. Client validation improves responsiveness; server validation is mandatory for security. Never trust client-only validation.
Libraries like React Hook Form and Formik reduce boilerplate. Zod schemas shared between client and server ensure consistent rules. Display field-level errors with aria-describedby linking inputs to error messages.
- Client UX + server security
- Schema libraries (Zod) shared client/server
- aria-describedby for error messages
Uncontrolled and Hybrid Patterns
Uncontrolled inputs use refs to read DOM values on submit — less rerender overhead for large forms. React Hook Form defaults to uncontrolled with refs internally. Hybrid approaches register fields while managing validation in React.
Choose uncontrolled when performance matters on large forms and validation is submit-time. Choose controlled when live formatting, dependent fields, or instant feedback are required.
- Refs for uncontrolled read on submit
- React Hook Form uses refs internally
- Pick pattern based on UX needs
Accessibility in Forms
Associate labels with inputs via htmlFor/id. Group related fields with fieldset/legend. Indicate required fields visually and with aria-required. Focus the first invalid field on submit failure.
Keyboard navigation must reach all interactive elements in logical order. Do not disable submit buttons without explanation — show validation errors instead so users understand what to fix.
- Label every input
- Fieldset for groups
- Focus management on errors
Server Actions and Progressive Enhancement
Next.js Server Actions accept FormData from native form submit — forms work without JavaScript. useActionState (or useFormState) tracks pending state and returned errors from the server action.
Optimistic updates with useOptimistic improve perceived speed for mutations. Always handle failure rollback and show server validation errors returned from actions.
- Native form submit to Server Actions
- useActionState for pending/errors
- useOptimistic for instant feedback
'use client';
import { useActionState } from 'react';
const [state, action, pending] = useActionState(submitForm, null);