Overview
Multi-step forms (wizards) break long forms into manageable chunks, but they introduce serious accessibility problems when step transitions are visual-only. A sighted user sees the step indicator change and the new fields appear, but a screen reader user may be stranded at the top of the page with no idea that the form has moved to step 2. The step indicator itself is often a set of styled spans with no semantic meaning — screen readers cannot determine which step is current. WCAG 2.2 introduced 3.3.7 Redundant Entry, which requires that information previously entered by the user must be auto-populated or available for selection if needed again in a later step.
WCAG Criteria:
- 3.3.7 Redundant Entry — information previously entered must not be requested again in a later step unless re-entry is essential
- 2.4.2 Page Titled — each step should update the page or section heading to reflect the current step
- 3.3.1 Error Identification — validate the current step before allowing the user to proceed
Key requirements:
- Use an
<ol>for the step indicator so screen readers convey the sequence and total number of steps - Mark the current step with
aria-current="step"so assistive technology can identify it programmatically - On step transition, move focus to the new step’s heading (with
tabindex="-1") so screen readers announce the new context - Validate the current step’s fields before advancing — do not let the user skip to step 2 with empty required fields
- Auto-populate or display previously entered data when returning to a step (WCAG 3.3.7 Redundant Entry)
- Provide Back and Next buttons so users can navigate between steps without losing data
Wizard Steps
Silent Step Change vs. Managed Focus Wizard
<!-- Step indicator with no semantic structure or current-step indication -->
<div style="display: flex; gap: 8px;">
<span>1</span>
<span>2</span>
</div>
<!-- Step 1 -->
<div id="step1">
<p>Step 1: Personal Info</p>
<label>Name</label>
<input type="text" />
<label>Email</label>
<input type="email" />
<button onclick="
document.getElementById('step1').style.display = 'none';
document.getElementById('step2').style.display = 'block';
">Next</button>
</div>
<!-- Step 2 — focus stays at page top, no Back button -->
<div id="step2" style="display: none;">
<p>Step 2: Address</p>
<label>Street</label>
<input type="text" />
<label>City</label>
<input type="text" />
<button>Submit</button>
</div>Step 1: Personal Info
<!-- Semantic step indicator with aria-current -->
<nav aria-label="Form progress">
<ol>
<li aria-current="step">1</li>
<li>2</li>
</ol>
</nav>
<!-- Step 1 — heading receives focus on Back navigation -->
<div id="step1">
<h3 id="heading1" tabindex="-1">Step 1 of 2: Personal Info</h3>
<label for="name">Name</label>
<input id="name" type="text" required />
<label for="email">Email</label>
<input id="email" type="email" required />
<div id="error" role="alert"></div>
<button onclick="
if (!name.value || !email.value) {
error.textContent = 'Please fill in all required fields.';
return;
}
step1.style.display = 'none';
step2.style.display = 'block';
heading2.focus();
">Next</button>
</div>
<!-- Step 2 — heading receives focus on Next navigation -->
<div id="step2" style="display: none;">
<h3 id="heading2" tabindex="-1">Step 2 of 2: Address</h3>
<label for="street">Street</label>
<input id="street" type="text" />
<label for="city">City</label>
<input id="city" type="text" />
<button onclick="
step2.style.display = 'none';
step1.style.display = 'block';
heading1.focus();
">Back</button>
<button>Submit</button>
</div>Step 1 of 2: Personal Info
What’s wrong with the silent step change?
- The step indicator uses unsemantic
<span>elements — screen readers have no way to determine the total number of steps or which step is current - No
aria-current="step"— the visual highlight on the active step number is invisible to assistive technology - When the user clicks “Next”, focus stays wherever it was — a screen reader user has no indication that new form fields have appeared
- No validation before advancing — the user can proceed with empty required fields and encounter errors later
- No “Back” button — users cannot return to a previous step to review or correct their input
- Previously entered data may be lost when navigating between steps, violating WCAG 3.3.7
What the screen reader announces:
| Action | Inaccessible version | Accessible version |
|---|---|---|
| Reading step indicator | ”1 2” (meaningless text) | “Form progress, navigation, list, 2 items, Step 1, current step” |
| Clicking Next | Nothing — focus stays at button | ”Step 2 of 2: Address, heading level 3” |
| Clicking Back | N/A (no Back button) | “Step 1 of 2: Personal Info, heading level 3” |
| Empty fields on Next | Nothing — silently advances | ”Please fill in all required fields before continuing” (via role=“alert”) |
Why aria-current="step" matters:
aria-current="step"is specifically designed for step indicators in wizards — it tells screen readers “this is the step the user is currently on”- Without it, a screen reader user navigating the step indicator hears a list of numbers with no way to know which is active
- The value
"step"(not"true"or"page") is the semantically correct token for multi-step processes
Why focus management is critical:
- When content changes dynamically, screen readers continue reading from the current cursor position
- If focus is not moved to the new step heading, the user may not realize the form has changed at all
tabindex="-1"on the heading allows programmatic focus without adding the heading to the natural tab order
Key Teaching Points
| Technique | Purpose | Where to apply |
|---|---|---|
<ol> for step indicator | Convey sequence and total step count | On the step indicator container |
aria-current="step" | Identify the current step programmatically | On the active <li> in the step indicator |
tabindex="-1" + .focus() | Move focus to new step on transition | On each step’s heading element |
role="alert" | Announce validation errors immediately | On the error message container |
| Validate before advancing | Prevent skipping required fields | In the Next button’s click handler |
| Preserve entered data | Comply with WCAG 3.3.7 Redundant Entry | Retain input values when navigating Back |