Forms & Validation
Forms are where most real user interaction happens — and where frontends get surprisingly complex. State, validation, accessibility, async checks, and error UX all collide in one component.
Two principles cut through that complexity: validate against a schema you can reuse on the client and server, and make errors accessible so every user knows what went wrong and how to fix it.
TL;DR
- Use a form library (React Hook Form, Formik) for anything non-trivial.
- Validate against a schema (Zod, Yup) you can share with the backend.
- Validate on blur and submit, not on every keystroke.
- Make errors accessible:
aria-invalid,aria-describedby,role="alert". - Client validation is UX — the server must validate too.
Quick Example
React Hook Form with a Zod schema — one source of truth for shape and messages:
Core Concepts
Controlled vs uncontrolled
Controlled inputs store every keystroke in React state (re-render per change); uncontrolled inputs let the DOM hold the value and read it on submit. React Hook Form is uncontrolled-first, which minimizes re-renders.
Validation layers
- Client-side — instant feedback and better UX.
- Schema-based — Zod/Yup define rules once, reusable on client and server.
- Server-side — the only validation that's a security boundary.
Timing
Validate on blur (when leaving a field) and on submit (everything). Validate on change only for critical fields, and debounce async checks (e.g. "username taken").
Best Practices
Make errors accessible
Link errors to inputs, announce them with role="alert", indicate required fields, and move focus to the first error on submit. See Accessibility.
Share schemas with the backend
A Zod schema validates the form and the API request, so the rules can't drift. See API Security for server-side validation.
Comparison
Common Mistakes
Validating on every keystroke
Trusting client validation for security
FAQ
React Hook Form or Formik?
React Hook Form for most new projects — it's uncontrolled-first, so it minimizes re-renders and ships a smaller bundle. Formik is fine for simple forms or existing codebases, but it re-renders more and is less actively favored now.
When should I validate — on change, blur, or submit?
On blur and on submit by default. On-change validation while someone is mid-typing is noisy and frustrating; reserve it for high-stakes fields (like password confirmation) and debounce anything that hits the network.
Do I still need server-side validation if the client validates?
Always. Client validation is purely UX and is trivially bypassed (curl, devtools). The server is the security boundary, so it must re-validate every input. A shared schema lets you do both without duplication.
What's schema validation and why use it?
A schema (Zod, Yup) declares the shape and rules of your data once. You derive both the form validation and the TypeScript types from it, and reuse it server-side — eliminating drift between what the form accepts and what the API accepts.
Related Topics
- React — Where form state lives
- Accessibility — Accessible inputs and errors
- TypeScript — Types inferred from schemas
- API Security — Server-side validation
- HTML Fundamentals — Native form elements