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

Start with Real Labels

Every form control needs an accessible name. The best default is a real <label>:

html
<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:

html
<!-- Avoid -->
<input name="email" type="email" placeholder="Email address">

Placeholder disappears as soon as the user types and may have poor contrast.

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:

html
<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:

html
<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:

html
<input
  id="username"
  name="username"
  aria-describedby="username-help username-error"
>

Error Messages

Place errors near the field and connect them:

html
<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:

html
<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:

  • Tab moves forward
  • Shift + Tab moves 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:

html
<input tabindex="5">
<input tabindex="1">

Prefer natural order:

html
<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:

html
<button type="submit">Save profile</button>
<button type="button">Preview</button>

Avoid clickable <div> elements:

html
<!-- 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

html
<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:

html
<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:

html
<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
  • for matches id
  • 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:

  1. Add visible labels to every field.
  2. Wrap radio buttons in fieldset and legend.
  3. Add help text for password requirements.
  4. Connect help text using aria-describedby.
  5. 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.