Accessible Forms Errors
Label inputs, associate errors with fields, announce failures, and avoid placeholder-only forms that break accessibility.
- accessibility
- accessible-forms
Broken forms are the highest-traffic a11y failures: missing labels, errors only in red borders, and focus that never moves to the problem. Accessible forms are mostly correct HTML plus clear error UX.
Docs: WCAG Forms tutorials, MDN labels.
Labels are mandatory
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email" />
Placeholder is not a label — it disappears on input and has weaker SR support.
<!-- Bad -->
<input placeholder="Email" />
Associate errors with fields
<label for="email">Email</label>
<input
id="email"
type="email"
aria-invalid="true"
aria-describedby="email-hint email-err"
/>
<p id="email-hint">We’ll never share your email.</p>
<p id="email-err" role="alert">Enter a valid email address.</p>
aria-invalid="true"when the field fails validationaria-describedbylists hint + error idsrole="alert"or a live region for new errors
Summary errors at top
For multi-field forms, provide a summary that links to fields:
<div role="alert" tabindex="-1" id="form-errors">
<p>Please fix 2 errors:</p>
<ul>
<li><a href="#email">Email is required</a></li>
<li><a href="#password">Password is too short</a></li>
</ul>
</div>
Move focus to the summary on failed submit — focus management.
Don’t rely on color alone
Red outline + text message. See do not rely on color alone.
Required fields
<label for="name">Name <span aria-hidden="true">*</span></label>
<input id="name" required aria-required="true" />
Visible required indicator plus programmatic required.
Inline validation timing
- Prefer validate on submit first for less noise.
- On blur validation should not steal focus unexpectedly.
- Announce async server errors via live region or alert.
React sketch
function Field({
id,
label,
error,
...props
}: {
id: string;
label: string;
error?: string;
} & React.ComponentPropsWithoutRef<'input'>) {
return (
<div>
<label htmlFor={id}>{label}</label>
<input
id={id}
aria-invalid={error ? true : undefined}
aria-describedby={error ? `${id}-err` : undefined}
{...props}
/>
{error ? (
<p id={`${id}-err`} role="alert">
{error}
</p>
) : null}
</div>
);
}
Footguns
- Clickable divs as inputs.
- Errors in
titletooltips only. - Disabling submit without explaining why.
aria-labelduplicating visible label incorrectly.
Interview out-loud answer
“Every input needs a visible label. Errors use text linked via aria-describedby and aria-invalid, not color alone. On failed submit I focus an error summary. Placeholder is never the only label.”
Disabled submit buttons
Disabling Submit until the form is “valid” can hide why the user can’t proceed. Prefer keeping Submit active and showing errors on activation, or pair disabled state with visible helper text: “Enter email and password to continue.”
Extra practice
Write a minimal demo in a scratch file or the playground: one happy path, one failure path, and one boundary input. If you cannot exhibit a bug that the pattern prevents, you do not own the concept yet — re-read the primary docs linked below and tighten the example until the failure is obvious.
Notes from real codebases
Teams that succeed here keep the rules mechanical: lint where possible, CI for the rest, and a short human checklist for what automation cannot see. Document exceptions with an owner name and a removal date so “temporary” escapes do not become permanent architecture.
Implementation notes
Wire this into real code on the next feature, not only a demo. Prefer the smallest change that encodes the rule — a shared helper, a lint rule, or a checklist item in the PR template. Revisit after a week of production traffic: if users or tests still hit the failure mode, the documentation (and the abstraction) are not sharp enough yet.
Related on this site
- Inclusive error messages
- ARIA labels and descriptions
- Focus management
- Record and Partial patterns for forms
- WCAG principles POUR
Further reading
Related guides
- Accessible Combobox PatternBuild or evaluate comboboxes with APG keyboard behavior, aria-expanded/activedescendant, and filterable listbox pairing.
- Accessible Menus PatternAPG menu and menubar keyboard model — arrow navigation, escape to close, aria-expanded, and when a disclosure is enough.
- Accessible Modals PatternsModal dialogs that work: focus trap, Escape, return focus, aria-modal, and why native dialog or APG patterns beat div soup.
- Accessible Names ComputationHow browsers compute accessible names from content, labels, aria-label, and labelledby — and how that powers Testing Library queries.
- Accessible SVGsDecorative vs informative SVG icons — aria-hidden, title/desc, role=img, and pairing icons with accessible names on controls.