Accessible Forms & Error Handling
Forms are where users transact — and inaccessible errors, missing labels, and broken validation flows silently exclude millions of people every day.
10 min read
Modern practice: persistent top-aligned labels, validation on blur (not every keystroke), and specific, recoverable error messages tied to the field with aria-describedby.
The full lesson
Forms are the most critical point of interaction in any digital product — account creation, checkout, contact, scheduling, configuration. They are also among the most frequently broken accessibility surfaces.
Missing visible labels, placeholder-only field hints, submit-only validation, and vague error messages like “Invalid input” all stack up into an experience that is hostile to screen reader users, people with cognitive disabilities, and keyboard-only users. WCAG 2.2 dedicates entire success criteria to form labels, error identification, and error suggestion. Non-compliance here directly hurts task completion rates and creates legal risk.
Why Forms Break More Than You Think
The quick fix — adding an aria-label to a field — misses the bigger problem. Most form failures cascade.
A placeholder used as a label disappears the moment the user starts typing, so they forget what the field is for. An error fires only on submit, so someone who filled in ten fields now has to hunt for the one that failed. The error message says “Required” with no hint of which field or what format is needed.
Each of those is a separate WCAG failure. They routinely happen together.
Modern best practice treats accessible forms as a design-system concern, not a remediation task. The patterns that make forms accessible are the same patterns that make forms usable for everyone.
Labels: The Foundation That Everything Else Depends On
Every form control — text inputs, selects, checkboxes, radios, textareas, date pickers, range sliders — must have a programmatically associated label. “Programmatically associated” means the browser can expose the label text to assistive technology, not just place it visually nearby.
There are four ways to associate a label, listed in preference order:
| Mechanism | HTML pattern | When to use |
|---|---|---|
for/id pairing | label for="email" + input id="email" | Default; use for all standard inputs |
| Wrapping label | label element wrapping both text and input | Checkboxes and radios; avoids needing matching IDs |
aria-labelledby | Points to the id of any visible text element | When the label is rendered by a different element (table column header, section heading) |
aria-label | String value directly on the control | Icon-only buttons, search inputs with adjacent button; last resort — not visible to sighted users |
The outdated pattern that still ships everywhere is using placeholder as the sole label. Placeholder text disappears the moment the user starts typing. This creates two compounding failures: the user loses context about the field, and the field has no programmatic label at all. The placeholder attribute is not semantically equivalent to a label. Screen readers handle it inconsistently — some read it as a label hint, others do not. It also fails WCAG 1.3.1 Info and Relationships and 2.4.6 Headings and Labels.
Floating labels — popularized by Material Design — are a partial improvement but carry their own problems. When the label is in the placeholder position, its contrast ratio is often below 3:1 because it needs to look like placeholder text. When it floats up, the small size may fall below the minimum font size for legibility. Animated float transitions can conflict with prefers-reduced-motion. The cognitive load of remembering a label’s original placeholder position is higher than a persistent top-aligned label. For most products, a visible top-aligned label that never moves is simpler and more robust.
Field Descriptions and Constraints
Labels name the field. Descriptions provide format instructions, constraints, or contextual help. Both must be programmatically associated.
Use aria-describedby to connect a visible hint element to its input. Screen readers read the hint text after the label and input type — giving the user the right information at the right moment, without interrupting the label announcement.
<label for="phone">Phone number</label>
<input
id="phone"
type="tel"
aria-describedby="phone-hint"
autocomplete="tel"
/>
<p id="phone-hint">Format: +1 (555) 000-0000</p>
Keep hint text concise. Place it below the label and above the input, or directly below the input — not after the submit button. If the constraints are complex, ask whether the form design can remove them rather than just explaining them.
Required fields need a clear pattern. The visual convention — a red asterisk — is meaningful only to sighted users and fails WCAG 1.3.3 Sensory Characteristics if the asterisk is the only indicator. The programmatic signal is the required attribute (or aria-required="true" for custom controls), which causes screen readers to announce the field as required. When you use an asterisk, include a legend at the top of the form: “Fields marked with * are required.” Place that legend before the first labeled field in DOM order so it is read first.
Inline Validation: When and How to Fire It
The outdated pattern is submit-only validation: the user fills the form, clicks submit, and a list of errors appears at the top. This forces a second pass through the entire form and breaks the mental connection between each field and its error.
The modern pattern is inline validation on blur — check each field when the user leaves it (the blur event), not while they are typing (the input event), and not only on submit. Firing on input is noisy and anxiety-inducing: it marks a phone number as invalid after the user types the first digit. Firing only on submit loses the opportunity to confirm correct inputs in real time.
The timing model:
- On blur: validate and show errors if the field is complete but invalid. Show a success indicator if the field is valid and confirmation is meaningful (for example, a username availability check).
- On input (after first blur): if the user is correcting a previously errored field, re-validate on each keystroke so they see the error clear immediately.
- On submit: re-validate all fields, move focus to the first error (or an error summary), and prevent submission.
<!-- Error state: input with aria-describedby pointing to the error message -->
<label for="email">Email address</label>
<input
id="email"
type="email"
aria-describedby="email-error"
aria-invalid="true"
value="not-an-email"
/>
<p id="email-error" role="alert">
Enter a valid email address — for example, [email protected]
</p>
aria-invalid="true" signals the error state to screen readers on the input itself. role="alert" on the error message causes it to be announced immediately when it appears in the DOM, without requiring the user to re-focus the field. Use role="alert" (which implies aria-live="assertive") for synchronous per-field errors. Use aria-live="polite" for less urgent messages that should not interrupt the user while a screen reader is already reading something.
Error Messages: Specific, Actionable, Human
The WCAG criteria most directly targeting error messages are:
- 3.3.1 Error Identification (AA): if an input error is automatically detected, describe the error to the user in text.
- 3.3.3 Error Suggestion (AA): if an input error is detected and a suggestion for correction is known, provide it — unless doing so would jeopardize security or purpose.
- 3.3.4 Error Prevention (AA): for legal, financial, or data-deletion submissions, the user must be able to review, confirm, or reverse the action.
A compliant error and a good error are not the same thing. “This field is required” passes 3.3.1 but fails 3.3.3 if the system knows what format is expected. “Enter a date in MM/DD/YYYY format” is both compliant and actionable.
Anti-patterns to avoid:
- “Invalid input” — names no field, gives no guidance
- “Please check your entries” — identifies nothing
- “Error 422” — a technical code exposed to the user
- Red icon without adjacent text — fails for colorblind users (1.4.1 Use of Color)
- Error text in a toast that auto-dismisses — the user may not have time to read it, and it is typically not associated with the field
Do
- Write error messages in plain language that name the field and explain the fix: “Phone number must be 10 digits, not including the country code.”
- Place the error message directly below the field it describes, with
aria-describedbylinking input to error text. - Use both color and an icon (with alt text or aria-label) plus text to communicate errors — never color alone.
- Fire validation on blur (when the user leaves the field), then re-validate live once the user begins correcting an errored field.
- On form submit with errors, move focus to either the first errored field or an error summary region at the top of the form.
Don't
- Use “Invalid input” or “Required” as complete error messages — they provide no actionable guidance.
- Show errors only after submit, especially in long or multi-step forms where the user has already moved far from the problematic field.
- Use
role="alert"on static helper text that does not change — it will be read aloud on every page load, which is disruptive and confusing. - Rely on color or icon alone to mark error state — both fail WCAG 1.4.1 Use of Color.
- Auto-dismiss error toasts — they may disappear before the screen reader announces them.
Error Summaries for Complex and Multi-Field Forms
For long forms or multi-step flows where multiple fields may fail at once, per-field inline errors alone are not enough. An error summary — a container near the top of the form that lists all errors — gives users an overview and a navigation mechanism.
The summary must:
- Appear above the first form field in DOM order, so it is encountered before the fields when tabbing forward.
- Receive programmatic focus when errors are shown, so screen reader users hear it immediately without having to discover it.
- Contain links to each errored field, using anchor
hrefpointing to the field’sid, so users can jump directly to each problem.
<div
id="error-summary"
role="alert"
tabindex="-1"
aria-labelledby="summary-heading"
>
<h2 id="summary-heading">2 errors need your attention</h2>
<ul>
<li><a href="#email">Email address: enter a valid email</a></li>
<li><a href="#phone">Phone: must be 10 digits</a></li>
</ul>
</div>
When the form is submitted with errors, call document.getElementById('error-summary').focus() to move focus to the summary. The role="alert" causes the content to be announced. The tabindex="-1" makes the element programmatically focusable without inserting it into the tab sequence during normal flow.
Grouping, Fieldsets, and Complex Controls
Radio buttons and checkboxes that belong to a logical group must be wrapped in a fieldset with a legend that names the group. Screen readers announce the legend text before each control within the group. This means users hear “Payment method — Credit card” and “Payment method — Bank transfer” rather than just “Credit card” and “Bank transfer” out of context.
<fieldset>
<legend>Preferred contact method</legend>
<label><input type="radio" name="contact" value="email" /> Email</label>
<label><input type="radio" name="contact" value="phone" /> Phone</label>
<label><input type="radio" name="contact" value="text" /> Text message</label>
</fieldset>
For custom components that replace native inputs — date pickers, autocomplete comboboxes, multi-select listboxes — follow the WAI-ARIA Authoring Practices Guide pattern for that widget type. The ARIA roles, states, and keyboard interactions are specified. Do not invent new patterns. Custom inputs that look like native inputs but lack ARIA roles, aria-expanded, aria-selected, and keyboard handling are invisible to screen readers and unusable by keyboard users.
The autocomplete Attribute and Cognitive Load
The autocomplete attribute is underused and undervalued. Correctly annotated fields enable browser autofill and password managers. This reduces the cognitive and motor effort of completing forms — and that reduction is critical for users with cognitive disabilities, motor impairments, and anyone on a mobile device.
WCAG 1.3.5 Identify Input Purpose (AA) requires that fields collecting personal data use the correct autocomplete token. This allows assistive technologies to identify the field’s purpose and present appropriate symbols or autofill suggestions.
Common tokens for personal data forms:
autocomplete="name",given-name,family-nameautocomplete="email"autocomplete="tel"autocomplete="street-address",address-line1,postal-code,countryautocomplete="new-password",current-passwordautocomplete="one-time-code"— for OTP/2FA fields
Setting autocomplete="off" on fields that collect personal data is a common but misguided security measure. It directly violates 1.3.5 and degrades the experience for all users.
Accessible Authentication: WCAG 2.2 SC 3.3.8
WCAG 2.2 added SC 3.3.8 Accessible Authentication (AA) to address login flows that require cognitive function tests — transcribing characters, solving puzzles, or remembering content not provided in the current session.
The criterion requires that if a cognitive function test is needed for authentication, at least one of the following must be true:
- An alternative authentication method exists that does not require a cognitive test.
- A mechanism is available to assist the user — such as copy-paste support, password manager compatibility, or providing the object or image directly in the login step.
In practice, this means: allow paste in password fields, do not disable autocomplete on login inputs, and if you use CAPTCHA, provide an audio alternative or a non-cognitive-test alternative. Blocking paste in password fields is one of the most widespread WCAG 2.2 failures. It is security theater — it makes the form harder for users and does nothing meaningful for security.