Forms and validation
Help people enter information, correct mistakes, and understand whether it saved.
Why this matters
A form should say what's needed, accept reasonable input, and keep the person's work safe when submitting doesn't finish cleanly. Picture a foreman assigning tomorrow's shift: name, site, crew — on a phone, with bad signal.
What to understand
Ask only for what this outcome needs. Use clear labels, sensible input types, autocomplete where it helps, and help text at the moment of doubt. Split a long form into steps when grouping aids understanding; extra steps also add navigation and recovery work.
Browser checks are convenience, not authority. The server must separately prove who you are, check what you're allowed to do, and validate each important action. A database constraint is a rule stored data can't break.
Watch for
- Flagging an error while someone is still typing a valid value.
- Every field error announced assertively, burying the message in overlapping speech.
- Important errors living only in a vanishing toast.
- An unknown outcome treated as a clear rejection and blindly retried.
- Sensitive fields stashed in browser storage without thinking.
- A form library added when native forms and framework actions already handle the case.
Strong default
Check on submit first; check on blur or change after an error is known. Keep input on rejection, say what success really means, and keep important errors inline. Match timing to the field and the stakes.
When this doesn't apply
A form library pays off when field wiring, changing fields, rules across fields, or client feedback get hard to coordinate. Keep the approach you have when it fits. Ask your agent: "does our current form setup already handle this, or do we need the library?" Extra steps aid understanding but add navigation and recovery work — don't split without that tradeoff.
In practice
| Layer | Purpose |
|---|---|
| Native input constraints | Immediate feedback for required fields, format, and bounds. |
| Client validation | Helpful domain feedback before a round trip. |
| Server validation | Validate untrusted input and enforce business rules. |
| Database constraints | Protect integrity when requests race or bypass the UI. |
If this project uses React Hook Form with Zod, each layer gets a home: the schema defines the rules once, the form resolves against it for instant feedback, and the server parses with the same schema before saving. Ask your agent: "do client and server share one schema, and where does each layer's error surface?"
Don't flag an error while someone is still typing a valid value. Checking on submit works well at first; checking on blur or change can help after an error is known. Match the timing to the field and the stakes.
<label htmlFor="project-name">Project name</label>
<input
id="project-name"
name="name"
required
aria-invalid={Boolean(error)}
aria-describedby={error ? 'name-error' : 'name-help'}
/>
{error ? (
<p id="name-error">{error}</p>
) : (
<p id="name-help">Use a name your team will recognize.</p>
)}Here error is a safe, user-facing message from the form. On failed submit, move focus to the first bad field or an error summary, depending on the flow. Don't announce every field error assertively; overlapping speech buries the message.
Say when work is in flight, keep input when it's rejected, and say what success really means. If the assignment was saved but the notification email is work that finishes after the reply, say so. Keep important errors inline; a vanishing toast is poor recovery.
Treat an unknown outcome apart from a clear rejection. A lost response can follow a successful save. Check the result or reuse a server-backed safe-retry key before trying again.
For an expired session, pick a safe return path and draft rule. Don't stash sensitive fields in browser storage without thinking. For cancelling, use undo or confirmation only when the loss and recovery odds justify it.
Verify
Submit with keyboard only, fix bad input, interrupt a request, retry safely, and come back from another step. Check that labels, waiting text, errors, and long content fit without hiding the next step.
Related skills
Use $flow for a multi-step task and $states for missing behavior. Continue with error handling.
Last updated on