Accessible Forms
Accessible form design patterns including labels, validation, error handling, and multi-step forms
You are an expert in accessible form patterns for building accessible web applications. ## Key Points 1. A `<label>` element with a matching `for`/`id` pair (preferred). 2. A wrapping `<label>` element. 3. `aria-label` (when a visible label is not feasible). 4. `aria-labelledby` pointing to another element's ID. - Identify the field in error by name. - Describe what went wrong in plain language. - Suggest how to fix it. - Associate the error message with the input using `aria-describedby`. - Move focus to the first error or to an error summary. - Click on every `<label>` and verify it focuses the associated input. - Submit the form with empty required fields and confirm that error messages are announced by screen readers and that focus moves to the error summary or the first invalid field. - Verify every input has an accessible name in the browser's accessibility tree. ## Quick Example ```html <div> <label for="full-name">Full name</label> <input type="text" id="full-name" name="fullName" autocomplete="name" required> </div> ```
skilldb get accessibility-skills/Accessible FormsFull skill: 229 linesAccessible Forms — Web Accessibility
You are an expert in accessible form patterns for building accessible web applications.
Core Philosophy
Overview
Forms are the primary way users interact with web applications—logging in, searching, submitting data, and configuring settings. Inaccessible forms are one of the most common barriers on the web. Accessible forms require proper labeling, clear instructions, descriptive error messages, logical grouping, and keyboard operability.
Core Concepts
Every input needs an accessible name
An input's accessible name can come from:
- A
<label>element with a matchingfor/idpair (preferred). - A wrapping
<label>element. aria-label(when a visible label is not feasible).aria-labelledbypointing to another element's ID.
Placeholder text is not an accessible name—it disappears on input and often has insufficient contrast.
Grouping related controls
Use <fieldset> and <legend> to group related inputs (radio buttons, checkboxes, address fields). Screen readers announce the legend text before each control in the group, providing context.
Error handling principles
- Identify the field in error by name.
- Describe what went wrong in plain language.
- Suggest how to fix it.
- Associate the error message with the input using
aria-describedby. - Move focus to the first error or to an error summary.
Implementation Patterns
Basic labeled input
<div>
<label for="full-name">Full name</label>
<input type="text" id="full-name" name="fullName" autocomplete="name" required>
</div>
Required field indication
<p id="required-hint">Fields marked with <span aria-hidden="true">*</span>
<span class="sr-only">asterisk</span> are required.</p>
<div>
<label for="email">
Email address <span aria-hidden="true">*</span>
</label>
<input
type="email"
id="email"
name="email"
required
aria-required="true"
aria-describedby="required-hint"
autocomplete="email"
>
</div>
Fieldset for related controls
<fieldset>
<legend>Preferred contact method</legend>
<div>
<input type="radio" id="contact-email" name="contact" value="email">
<label for="contact-email">Email</label>
</div>
<div>
<input type="radio" id="contact-phone" name="contact" value="phone">
<label for="contact-phone">Phone</label>
</div>
<div>
<input type="radio" id="contact-sms" name="contact" value="sms">
<label for="contact-sms">Text message</label>
</div>
</fieldset>
Inline error messages
<div>
<label for="password">Password</label>
<input
type="password"
id="password"
name="password"
required
aria-required="true"
aria-invalid="true"
aria-describedby="password-error password-hint"
autocomplete="new-password"
>
<p id="password-hint" class="hint">Must be at least 8 characters.</p>
<p id="password-error" class="error" role="alert">
Error: Password is too short. Enter at least 8 characters.
</p>
</div>
Error summary at the top of the form
<div role="alert" id="error-summary" tabindex="-1">
<h2>There are 2 errors in this form</h2>
<ul>
<li><a href="#email">Email address is required</a></li>
<li><a href="#password">Password must be at least 8 characters</a></li>
</ul>
</div>
function handleSubmit(event) {
event.preventDefault();
const errors = validateForm();
if (errors.length > 0) {
renderErrorSummary(errors);
// Focus the error summary so screen readers announce it
document.getElementById('error-summary').focus();
} else {
submitForm();
}
}
Accessible select / custom dropdown
<!-- Native select: always the most accessible option -->
<label for="country">Country</label>
<select id="country" name="country" autocomplete="country-name">
<option value="">Select a country</option>
<option value="us">United States</option>
<option value="ca">Canada</option>
<option value="uk">United Kingdom</option>
</select>
Multi-step form (wizard)
<nav aria-label="Form progress">
<ol>
<li aria-current="step">Step 1: Personal info</li>
<li>Step 2: Preferences</li>
<li>Step 3: Review</li>
</ol>
</nav>
<form>
<fieldset>
<legend>Step 1 of 3: Personal information</legend>
<div>
<label for="first-name">First name</label>
<input type="text" id="first-name" autocomplete="given-name" required>
</div>
<div>
<label for="last-name">Last name</label>
<input type="text" id="last-name" autocomplete="family-name" required>
</div>
<button type="button" onclick="goToStep(2)">Next: Preferences</button>
</fieldset>
</form>
Autocomplete attributes
<!-- Use autocomplete to help users and password managers -->
<input type="text" autocomplete="given-name" name="firstName">
<input type="text" autocomplete="family-name" name="lastName">
<input type="email" autocomplete="email" name="email">
<input type="tel" autocomplete="tel" name="phone">
<input type="text" autocomplete="street-address" name="address">
<input type="text" autocomplete="postal-code" name="zip">
<input type="password" autocomplete="new-password" name="password">
<input type="text" autocomplete="one-time-code" name="otp" inputmode="numeric">
Testing & Validation
- Click on every
<label>and verify it focuses the associated input. - Submit the form with empty required fields and confirm that error messages are announced by screen readers and that focus moves to the error summary or the first invalid field.
- Verify every input has an accessible name in the browser's accessibility tree.
- Tab through the entire form and confirm the order is logical.
- Test with browser autofill to verify
autocompleteattributes work correctly. - Test with voice control software (Dragon, Voice Control) to confirm labels are speakable targets.
Best Practices
- Use native HTML form elements (
<input>,<select>,<textarea>) with visible<label>elements as the default approach. - Set the correct
autocompleteattribute on every input to assist users and password managers. - Validate on submit rather than on every keystroke to avoid overwhelming screen reader users with premature error announcements.
Common Pitfalls
- Using
placeholderas a substitute for<label>—the placeholder disappears when the user types, leaving no visible label. - Announcing errors on every keystroke with
aria-live, which creates a barrage of interruptions for screen reader users before they finish typing.
Anti-Patterns
Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.
Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.
Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add accessibility-skills
Related Skills
Aria Patterns
ARIA roles, states, and properties for building accessible custom widgets and UI components
Axe Testing
Automated accessibility testing with axe-core, including CI integration, custom rules, and result analysis
Color Contrast
Color contrast ratios, visual accessibility, and inclusive design for users with low vision or color blindness
Focus Management
Focus management strategies for single-page applications, modals, route changes, and dynamic content
Keyboard Navigation
Keyboard navigation patterns, focus order, and shortcut design for fully keyboard-accessible interfaces
Screen Reader Compat
Building web content that works correctly with screen readers like NVDA, JAWS, and VoiceOver