Forms & Controls
Form patterns
Labels, required fields, inline error handling, error summary, aria-describedby, and autocomplete attributes.
Last verified against WCAG 2.2 and WAI-ARIA APG 1.2 on 2026-07-02.
Implementation
Field-level aria-invalid/aria-describedby, a focus-managed error summary, and non-color error signaling.
forms-pattern.tsx
function handleSubmit(e) {
e.preventDefault();
const nextErrors = validate(values);
setErrors(nextErrors);
setSubmitted(true);
if (Object.keys(nextErrors).length > 0) {
// Move focus into the summary so SR users hear it immediately.
requestAnimationFrame(() => summaryRef.current?.focus());
}
}
<div ref={summaryRef} tabIndex={-1} role="alert">
<p>There are 2 problems with your submission</p>
<ul>
<li><button onClick={() => focusField("name")}>Enter your full name.</button></li>
<li><button onClick={() => focusField("email")}>Enter a valid email address.</button></li>
</ul>
</div>
<input
id="email"
aria-invalid={submitted && !!errors.email}
aria-describedby={submitted && errors.email ? "email-error" : undefined}
/>
{submitted && errors.email && <p id="email-error">Error: {errors.email}</p>}Live demo
Required roles, states & properties
| Element | Attribute | Why |
|---|---|---|
| Every input | <label htmlFor> associated by id | The only reliable way to give an input a persistent accessible name — placeholder text disappears on input and isn't treated as a label by many screen readers. |
| Required inputs | required + aria-required="true" | Communicates required state to both native HTML validation and assistive tech; paired with visible, non-color-only text explaining what the asterisk means. |
| Invalid inputs | aria-invalid="true" | Marks the field as currently in an error state, announced as "invalid" when the field receives focus. |
| Invalid inputs | aria-describedby → error message id | Links the field to its specific error text so the message is announced right after the field's name and invalid state. |
| Error summary container | role="alert" + tabIndex={-1} | role="alert" announces the summary as soon as it appears; tabIndex={-1} makes it programmatically focusable via ref (without adding it to the normal tab order) so focus can be moved there on failed submit. |
| Common fields | autocomplete="name" | "email" | "tel" | Identifies input purpose (SC 1.3.5) so browsers/password managers autofill correctly and AT can convey the expected kind of data. |
Keyboard interaction model
| Key | Behavior |
|---|---|
| Tab / Shift+Tab | Moves between labeled fields and the submit button in reading order. |
| Enter (in a text field) | Submits the form, triggering validation. |
| Any key (after failed submit, focus in summary) | Tab from the error summary moves to the first listed error link/button; activating it (Enter/Space) moves focus directly to the corresponding invalid field. |
Focus management rules
- On a failed submit, focus moves programmatically (via ref, not just visually) to the error summary.
- The error summary is focusable (tabIndex={-1}) but not part of the normal tab order, so it doesn't add an extra unexpected stop on a successful pass through the form.
- Activating an error summary link moves focus directly to the corresponding invalid field, never to an unrelated element.
- Focus is never moved automatically except in direct response to a user action (submit) — receiving focus on a field never itself triggers validation or a context change (SC 3.2.1/3.2.2).
WCAG 2.2 success criteria mapping
| SC | Name | Level | Why it applies |
|---|---|---|---|
| 1.3.5 | Identify Input Purpose | AA | Form fields collecting common user info use correct autocomplete attributes. |
| 3.3.1 | Error Identification | A | Input errors are identified and described to the user in text. |
| 3.3.2 | Labels or Instructions | A | Labels or instructions are provided when content requires user input. |
| 3.3.3 | Error Suggestion | AA | If an input error is detected and suggestions are known, they are provided to the user. |
| 3.3.7 | Redundant Entry | A | Information previously entered is auto-populated or available for the user to select. |
| 4.1.2 | Name, Role, Value | A | For all UI components, name, role, and value are programmatically determinable; states and changes are announced. |