Accessible Forms
Introduction
Accessible forms are easier for everyone: keyboard users, screen reader users, mobile users, and people recovering from mistakes. This chapter covers labels, fieldset, legend, error text with aria-describedby, radio and checkbox groups, focus order, and practical form patterns.
Prerequisites
- Form validation
- Basic form controls from Form basics
Start with Real Labels
Every form control needs an accessible name. The best default is a real <label>:
<label for="email">Email address</label>
<input id="email" name="email" type="email" autocomplete="email">The for value must match the input id.
Benefits:
- screen readers announce the field name
- clicking the label focuses the control
- mobile users get a larger tap target
- error messages make more sense
Do not rely on placeholder text as the only label:
<!-- Avoid -->
<input name="email" type="email" placeholder="Email address">Placeholder disappears as soon as the user types and may have poor contrast.
Group Related Controls with fieldset and legend
Radio buttons and checkbox groups need a group label:
legend tells users what the options belong to. Without it, a screen reader might announce "Free, radio button" without enough context.
Checkbox group:
<fieldset>
<legend>Topics you want to learn</legend>
<label><input type="checkbox" name="topics" value="html"> HTML</label>
<label><input type="checkbox" name="topics" value="css"> CSS</label>
<label><input type="checkbox" name="topics" value="js"> JavaScript</label>
</fieldset>Connect Help Text with aria-describedby
Use aria-describedby when a control has extra instructions:
<label for="password">Password</label>
<input
id="password"
name="password"
type="password"
minlength="8"
aria-describedby="password-help"
>
<p id="password-help">Use at least 8 characters.</p>The screen reader announces both the label and the help text.
You can reference multiple ids:
<input
id="username"
name="username"
aria-describedby="username-help username-error"
>Error Messages
Place errors near the field and connect them:
<label for="email">Email</label>
<input
id="email"
name="email"
type="email"
required
aria-invalid="true"
aria-describedby="email-error"
>
<p id="email-error">Enter a valid email address.</p>aria-invalid="true" tells assistive technology the field currently has an error. Add it only when the field is actually invalid, not on page load before the user does anything.
Tip
Write Human Error Text
"Invalid input" is vague. "Enter a valid email address, such as name@example.com" tells users how to recover.
Required Fields
Make required status clear visually and programmatically:
<label for="email">Email <span aria-hidden="true">*</span></label>
<input id="email" name="email" type="email" required>
<p>Fields marked with * are required.</p>Do not rely only on color or an asterisk without explanation.
Keyboard Behavior and Focus Order
Users should be able to complete the form with only the keyboard:
Tabmoves forwardShift + Tabmoves backward- Space toggles checkboxes
- Arrow keys move through radio groups in many browsers
- Enter submits a form from many text fields
The focus order follows document order. Write markup in a logical sequence instead of using positive tabindex.
Avoid:
<input tabindex="5">
<input tabindex="1">Prefer natural order:
<label for="first-name">First name</label>
<input id="first-name" name="firstName">
<label for="last-name">Last name</label>
<input id="last-name" name="lastName">Buttons: Use Real Buttons
Use <button> for actions:
<button type="submit">Save profile</button>
<button type="button">Preview</button>Avoid clickable <div> elements:
<!-- Avoid -->
<div onclick="submitForm()">Submit</div>Real buttons receive keyboard focus, activate with keyboard keys, and have built-in semantics.
Selects, Radios, and Checkboxes
Select
<label for="country">Country</label>
<select id="country" name="country">
<option value="">Choose a country</option>
<option value="cn">China</option>
<option value="us">United States</option>
</select>Use a blank first option when no default selection should be assumed.
Radio group
Use when exactly one option should be selected:
<fieldset>
<legend>Billing cycle</legend>
<label><input type="radio" name="billing" value="monthly"> Monthly</label>
<label><input type="radio" name="billing" value="yearly"> Yearly</label>
</fieldset>Checkbox group
Use when multiple options may be selected:
<fieldset>
<legend>Notification channels</legend>
<label><input type="checkbox" name="channels" value="email"> Email</label>
<label><input type="checkbox" name="channels" value="sms"> SMS</label>
<label><input type="checkbox" name="channels" value="push"> Push</label>
</fieldset>Complete Accessible Form Example
Quick Accessibility Checklist
- Every control has a visible label
formatchesid- Radio and checkbox groups use
fieldset+legend - Help text is connected with
aria-describedby - Error text is specific and near the field
- Invalid fields get
aria-invalid="true"when invalid - Required status is explained
- Natural keyboard tab order works
- Buttons are real
<button>elements
Mini Exercise
Take your registration form from the previous chapter and improve it:
- Add visible labels to every field.
- Wrap radio buttons in
fieldsetandlegend. - Add help text for password requirements.
- Connect help text using
aria-describedby. - Tab through the form without a mouse and confirm the order makes sense.
FAQ
Do I need ARIA for every form?
No. Start with native HTML: label, input, fieldset, legend, button. Add ARIA only when native semantics need extra help.
Is placeholder text accessible?
It can be announced in some cases, but it is not a replacement for a visible label. Use placeholder only as an example or hint.
Should errors appear on page load?
Usually no. Show errors after submit or after a user leaves a field, not before they interact.
Can I hide labels visually?
Sometimes, for compact search fields. If you hide labels, use a tested visually-hidden CSS utility, not display: none, so screen readers can still access them.
What comes next?
Semantic HTML layout — page-level structure with header, main, footer, nav, section, article, and aside.