Forms & Validation
Native inputs first, validation that waits until people are done, server errors mapped onto fields, accessible error messages, and forms that never lose work.
Forms are where users give you something: their money, their data, their time. A field that rejects valid input, an error that doesn't say what's wrong, a submit that wipes what someone typed: each turns a user who wanted to finish into one who gives up. Most of what follows is about not punishing people for the gap between what they entered and what the system expected.
Native HTML First
Every piece of a form the browser handles is one you don't build, test or get wrong.
- A visible
<label>for every input, connected withfor/id. Clicking it focuses the field, screen readers announce it, and it's still there after the user starts typing, which a placeholder isn't. - The right
type.email,tel,urlandnumberchange the mobile keyboard, so someone typing a phone number gets a keypad. A usability win for one attribute. autocompletewith real tokens.given-name,email,street-address,postal-code,cc-number,one-time-codeand the rest are defined by the HTML standard and let the browser fill a whole checkout in one tap. Getting them wrong, or turning themoff, makes people retype what their browser already knows (and on login fields most browsers ignoreoffanyway, so password managers keep working, per MDN).- Constraint attributes (
required,minlength,pattern,min,max) give you constraint validation and the:invalidstate for free, with the rules in the markup where the next engineer will find them.
When to Validate
Validating on every keystroke tells people they're wrong while they're still typing: "Invalid email" after the first letter of every address anyone has ever entered. It's noise at best and alarming at worst, and it trains people to ignore the messages that matter.
I validate when the person has finished: on submit, and optionally when they leave a field they've already corrected once. The GOV.UK Design System, built on years of research with people who aren't comfortable online, goes further and validates only on submit. The exception is feedback that guides rather than judges: a character count nearing its limit, or password strength while someone chooses one.
The Server Is the Source of Truth
Client-side validation is a courtesy: fast feedback for the obvious cases, but anyone can bypass it, and the client can't know whether the email is already registered or the coupon expired a minute ago. The server validates everything, every time, so the form handles server errors as a first-class path. The API returns field-level failures as a 422 with an errors object keyed by field (Error Responses):
{
"message": "The given data was invalid.",
"errors": {
"email": ["An account with this email already exists."],
"address.postal_code": ["The postal code doesn't match the city."]
}
}
The form maps each key onto its field and shows the message there, exactly as for a client-side error; the user shouldn't be able to tell which side caught the problem. A 400 or 5xx isn't about any field, so it becomes a form-level message. This is why the API's error shape matters to the frontend: one consistent errors object means one piece of form code handles every endpoint.
Error Messages That Fix the Problem
An error message has one job: get the person to a valid submission. "Invalid input" fails it; "Enter a date in the format 31/12/2026" does it.
- Say what's wrong and how to fix it, in the person's terms, not the system's. "The card number is too short: it should be 16 digits", not "Validation failed: pan.length".
- Don't blame. "Enter your postcode" beats "You didn't enter a postcode".
- Be specific to the field and the rule that failed. One generic message per field hides which of three rules was broken.
WCAG makes the first two requirements: errors must be identified and described in text (3.3.1), and where a correction is known it should be suggested (3.3.3).
Accessible Errors
A red border tells sighted users something is wrong, and nobody else anything. These are the form-specific parts of Accessibility.
- Text, not just color. Every error has a message, ideally with an icon too.
- Attach the message to its field with
aria-describedby, so a screen reader announces it when the field gets focus, and mark the field witharia-invalid="true"(MDN). - After a failed submit, move focus. On a long form, focus an error summary at the top that links to each field; on a short one, focus the first invalid field. Either way, a keyboard or screen reader user lands on the problem instead of a submit button that silently did nothing. GOV.UK's error summary is a well-tested version, and it prefixes the page title with "Error:" so the problem is announced immediately.
Never Lose Work
The most expensive form bug is the one that clears the form: ten minutes on an application, one field fails, and it's back to blank.
- Keep every value on failure, client-side and server-side.
- Don't make people re-enter what they already gave you in the same flow (WCAG 3.3.7, Redundant Entry). Carry the shipping address into billing with a checkbox, not a retype.
- Save drafts of long forms locally or on the server as people go, so a crash, an expired session or a closed tab doesn't erase their work.
- Split long forms into steps with one topic each, and let people go back without losing later answers. Steps reduce what anyone has to hold in their head and localize errors to one page.
Double Submits
People double-click, and networks drop the response after the server did the work, so users click again. If submitting creates something (an order, a payment), two clicks can create two of it. Disabling the button in flight handles the double click, but not the retry after a lost response, which is the case that actually charges cards twice. Generate an idempotency key when the form is first shown and send it with every attempt: the button state is a courtesy; the key is the guarantee.
Password Managers and Paste
Blocking paste on password fields, or breaking autofill with unusual markup, pushes people toward short passwords they can type from memory, the opposite of what the restriction intends. NIST's digital identity guidelines say verifiers SHALL allow password managers and autofill and SHOULD permit paste, and WCAG 2.2's Accessible Authentication criterion treats paste and password managers as the way to avoid making people memorize or transcribe anything.
Use autocomplete="current-password" on sign-in and new-password on sign-up and password changes, so managers know whether to fill or generate; one-time-code lets the platform offer the code from an SMS without switching apps.
This Site
The site has no account forms, but the API reference's "Try it" console follows the same rules for its request form: every parameter has a visible label, the request body is checked as JSON before sending with a message that says what's wrong ("Body isn't valid JSON: …"), and a failed request leaves everything the user entered in place so they can fix it and send again.
References
Specifications
- WHATWG: HTML, Autofill, the autocomplete tokens and how browsers use them
- W3C: Understanding WCAG 2.2, Error Identification, errors described in text
- W3C: Understanding WCAG 2.2, Error Suggestion, suggesting how to fix an error
- W3C: Understanding WCAG 2.2, Redundant Entry, not asking for the same information twice
- W3C: Understanding WCAG 2.2, Accessible Authentication (Minimum), paste and password managers in sign-in
- NIST SP 800-63B: Authentication and Authenticator Management, allowing password managers, autofill and paste
Reference
- MDN: autocomplete, token list and browser behavior on login fields
- MDN: Constraint validation, built-in validation attributes and states
- MDN: aria-invalid, marking a field as invalid
- MDN: aria-describedby, connecting an error message to its field
- W3C WAI: Forms tutorial, user notifications, accessible success and error notifications
In practice
- GOV.UK Design System: Validation, validating on submit and keeping what users entered
- GOV.UK Design System: Error summary, the summary, focus management and the "Error:" title prefix
- web.dev: Sign-in form best practices, autocomplete and password manager friendly markup