LEARNING OBJECTIVES โต
- Understand why multi-step wizards dramatically improve conversion rates and cognitive ergonomics on long forms.
- Architect a single-form multi-step structure using multiple
<fieldset>panels with state persistence. - Implement step-by-step constraint validation using
checkValidity()andreportValidity()before transitioning steps. - Create fully accessible step progress indicators using ordered lists (
<ol>),aria-current="step", and programmatic focus management.
๐ The Mental Model & Story (Intuitive Foundation)
Imagine applying for a home mortgage loan. The application requires 85 individual data points: personal details, employment history, tax records, co-borrower disclosures, property assets, and loan terms.
If a bank gave you a single, endless webpage with 85 continuous fields, the sheer cognitive overload would cause massive user drop-off and frustration.
Instead, modern digital products break the process into a Multi-Step Wizard:
- Step 1 of 4: Personal Identity
- Step 2 of 4: Employment & Income
- Step 3 of 4: Property Information
- Step 4 of 4: Review & Final Submission
STEP 1 STEP 2 STEP 3 STEP 4
(Personal) ===> (Employment) ===> (Property) ===> (Review)
[ ACTIVE ] [ Pending ] [ Pending ] [ Pending ]
โ
โผ
+--------------------------------------------------------------------+
| STEP 1: PERSONAL IDENTITY |
| |
| Full Name: [ John Doe ] |
| Email: [ [email protected] ] |
| |
| [ Next Step > ]|
+--------------------------------------------------------------------+
A wizard decomposes a massive cognitive task into bite-sized, validated milestones. Each step acts as an isolated checkpoint, ensuring that errors are caught and corrected immediately before moving forward.
Technical Deep Dive & Specifications
Architecture: Single <form> vs. Multi-Page Architecture
There are two primary ways to engineer multi-step wizards:
ARCHITECTURAL MODELS:
1. Multi-Page (Traditional):
Page 1 (POST) โโ> Server Session โโ> Page 2 (POST) โโ> Page 3 (Submit)
2. Single-Form Client Wizard (Modern Single-Page Pattern):
<form id="wizard-form">
โโโ <fieldset class="wizard-step" data-step="1"> (Visible)
โโโ <fieldset class="wizard-step" data-step="2" hidden disabled>
โโโ <fieldset class="wizard-step" data-step="3" hidden disabled>
</form>
Why the Single-Form Pattern is Superior for Modern Frontends
- Unified Form State: All inputs exist within a single native
<form>, eliminating complex server session serialization or database draft tables for simple flows. - Native Submission: A single final submit button sends the complete
FormDatapayload in one unified HTTP POST. - Zero Hidden Validation Traps: By applying
disabledto inactive<fieldset>steps alongsidehidden, inactive steps are automatically exempted from constraint validation until activated.
Step Validation Algorithm
Before a user can transition from Step $N$ to Step $N+1$, you must validate all inputs within the active fieldset:
User Clicks "Next Step" Button
โ
โผ
Extract all form controls in active <fieldset>
(using activeFieldset.elements)
โ
โผ
Are all active inputs valid?
(Array.from(fieldset.elements).every(el => el.checkValidity()))
โ โ
YES NO
โ โ
โผ โผ
1. Disable & Hide Step N 1. Trigger activeFieldset.reportValidity()
2. Enable & Show Step N+1 2. Focus first invalid control
3. Move Focus to Step Heading 3. Halt step progression!
4. Update Step Indicator
function validateCurrentStep(stepFieldset) {
// Check validity of every control in this fieldset
const inputs = Array.from(stepFieldset.querySelectorAll('input, select, textarea'));
for (const input of inputs) {
if (!input.checkValidity()) {
input.reportValidity(); // Shows native browser tooltip
input.focus();
return false; // Block progression
}
}
return true; // All valid
}
Accessible Step Indicators with WAI-ARIA
A production-grade wizard must communicate progress to screen readers:
<nav aria-label="Onboarding Progress">
<ol class="step-indicator">
<li class="step-item" aria-current="step">
<span class="step-num">1</span>
<span class="step-title">Account</span>
</li>
<li class="step-item">
<span class="step-num">2</span>
<span class="step-title">Profile</span>
</li>
<li class="step-item">
<span class="step-num">3</span>
<span class="step-title">Confirmation</span>
</li>
</ol>
</nav>
<nav aria-label="...">: Declares a navigation landmark.<ol>: Conveys the ordered, sequential nature of the steps.aria-current="step": Informs screen readers (e.g. "Step 1, Account, current step").
๐ป Interactive Code Playground
Starter Code
Line-by-Line Code Breakdown
- Line 104โ118 (
<nav aria-label="Registration Progress">...): Builds an accessible step breadcrumb witharia-current="step". - Line 124โ154 (
<fieldset class="wizard-panel" ... hidden disabled>): Inactive panels are marked bothhidden(visually invisible) anddisabled(exempt from validation until shown). - Line 173โ187 (
showStep()): Handles panel visibility, enables/disables active controls, updates ARIA markers, and programmatically shifts focus to the step's<legend>for screen reader orientation. - Line 207โ214 (
input.reportValidity()): Forces browser constraint validation before allowing the user to advance to the next step.
Expected Browser Render Output
+-------------------------------------------------------------+
| (1) Credentials โโโ 2 Profile โโโ 3 Confirm |
| |
| Step 1: Account Credentials |
| |
| Work Email: |
| [________________________________________________________] |
| |
| Password (min 8 chars): |
| [________________________________________________________] |
| |
| [ Next Step ] |
+-------------------------------------------------------------+๐๏ธ Hands-On Exercise
๐ฏ The Challenge: Tenant Onboarding Wizard
Instructions:
- Construct a 2-step onboarding wizard inside a single
<form>:- Step 1 (
<fieldset id="step-org">): Organization Name (name="org_name", required) and Primary Domain (name="org_domain", required, type="text"). - Step 2 (
<fieldset id="step-billing" hidden disabled>): Credit Card Number (name="cc_num", required, pattern="[0-9]{16}") and Expiry (name="cc_exp", required, placeholder="MM/YY").
- Step 1 (
- Build an accessible ordered list indicator above the form displaying Step 1 and Step 2.
- Wire a "Proceed to Billing" button that validates Step 1 before transitioning to Step 2.
- Add a "Back to Org Details" button on Step 2 that returns to Step 1 without erasing previously entered values.
๐ Starter Code Sandbox
โ ๏ธ Common Pitfalls
- Using
display: noneWithoutdisabled: Hiding an inactive step with CSSdisplay: nonewhile leaving its fields enabled causes form submission to fail silently because the browser refuses to focus hidden invalid inputs. Always setfieldset.disabled = trueon hidden steps. - Losing Keyboard Focus During Step Transitions: When a user clicks "Next", the button might disappear, causing browser focus to reset to the top of
<body>. Always programmatically move focus to the new step's<legend>or first input (legend.focus()). - Submitting on Enter Key in Early Steps: Pressing the Enter key inside an input on Step 1 will attempt to submit the form immediately. Intercept the form's
submitevent or useevent.preventDefault()on Enter when not on the final step.
๐ก Pro Tips
- Auto-Persisting Drafts to
sessionStorage: Listen to the form'sinputevent and serialize active values tosessionStoragewith a key likewizard_draft_v1. If the user accidentally refreshes their browser on Step 3, restore the state seamlessly. - Summary Review Generation via
FormData: On the final review step, generate the summary table automatically by iteratingnew FormData(form).entries()rather than querying individual DOM elements manually.
๐ Key Takeaways
- Multi-step wizards minimize cognitive fatigue and increase form completion rates.
- Encapsulating all wizard steps in a single
<form>simplifies state management and submission payloads. - Always apply both
hiddenanddisabledto inactive fieldsets to bypass validation traps. - Validate each active step using
input.reportValidity()before transitioning. - Use
<ol>,aria-current="step", and programmatic focus shifting to ensure full WCAG accessibility. - --