LEARNING OBJECTIVES ⌵
- Structure multi-phase enterprise workflows using semantic
<fieldset>and<legend>boundaries. - Build accessible step indicators using ordered lists
<ol>andaria-current="step". - Master the JavaScript Constraint Validation API (
checkValidity(),reportValidity(),setCustomValidity(),validityobject). - Construct an accessible wizard state machine that prevents step advancement when active field constraints are violated.
📖 The Mental Model & Story (Intuitive Foundation)
Imagine filling out an application for a multi-million-dollar commercial loan or applying for a passport. If the government handed you a single 40-page sheet with 300 questions squeezed into one overwhelming scroll, cognitive overload would trigger a high rate of errors and abandonment.
Instead, bureaucratic institutions break the process into distinct sealed folders or stages:
- Stage 1: Identity & Legal Entity
- Stage 2: Infrastructure & Cloud Configuration
- Stage 3: Payment & SLA Agreement
- Stage 4: Verification & Final Provisioning
You only inspect and validate one folder at a time. The clerk will not let you proceed to Stage 2 until Stage 1 has been verified and stamped without errors.
In web engineering, an enterprise SaaS onboarding wizard operates on this exact principle. By wrapping each phase in a semantic <fieldset> with an explicit <legend>, binding it to an accessible step tracker with aria-current="step", and guarding phase transitions with the browser's native Constraint Validation API, we create a guided, accessible, fail-safe user experience.
Technical Deep Dive & Specifications
1. Multi-Step Wizard Architecture & Step State Machine
+----------------------------------------------------------------------------------------------------+
| ONBOARDING WIZARD SHELL (<form id="wizard-form" novalidate>) |
+----------------------------------------------------------------------------------------------------+
| <nav aria-label="Onboarding Progress"> |
| <ol class="step-tracker"> |
| <li class="completed"><span>1</span> Organization (Done)</li> |
| <li class="active" aria-current="step"><span>2</span> Cluster Specs (Current)</li> |
| <li class="pending"><span>3</span> Billing & Review</li> |
| </ol> |
| </nav> |
+----------------------------------------------------------------------------------------------------+
| FIELDSET 1: [hidden] (Organization Details) |
+----------------------------------------------------------------------------------------------------+
| FIELDSET 2: [Active Step] |
| <legend>Step 2: Kubernetes Cluster Sizing</legend> |
| ├── <label for="node-count">Node Count (1-100):</label> |
| │ <input type="number" id="node-count" min="1" max="100" required> |
| ├── <label for="cluster-region">Deployment Region:</label> |
| │ <select id="cluster-region" required>...</select> |
| └── <div role="alert" id="step-error-region" class="error-msg"></div> |
+----------------------------------------------------------------------------------------------------+
| FIELDSET 3: [hidden] (Billing & Review) |
+----------------------------------------------------------------------------------------------------+
| WIZARD ACTIONS |
| [ < Back ] -----------------------------------------------> [ Next Step > ] / [ Deploy Cluster ] |
+----------------------------------------------------------------------------------------------------+
2. Constraint Validation API Properties & Methods
| Property / Method | Type / Signature | Functional Specification & Enterprise Usage |
|---|---|---|
element.checkValidity() |
() => boolean |
Evaluates if the element satisfies all HTML5 constraints (required, pattern, min, max). Returns boolean without showing browser bubble. |
element.reportValidity() |
() => boolean |
Evaluates validity, fires the invalid event, and renders the browser's native error bubble tooltip if invalid. |
element.setCustomValidity(msg) |
(message: string) => void |
Sets a custom error string. If msg !== "", the element becomes permanently invalid until reset with "". |
element.validity.valueMissing |
boolean |
true if a required input is empty. |
element.validity.patternMismatch |
boolean |
true if value fails the regular expression in pattern="...". |
element.validity.rangeOverflow |
boolean |
true if value exceeds max="...". |
element.validity.customError |
boolean |
true if setCustomValidity() was called with a non-empty string. |
3. Step Transition & Validation Flow
[User clicks "Next Step"]
│
▼
[Get inputs in active <fieldset>]
│
▼
[Loop: input.checkValidity()]
├── ALL VALID? ──────► Hide current <fieldset> ──► Show next <fieldset> ──► Update aria-current="step"
│
└── ANY INVALID? ────► input.reportValidity() ──► Announce in aria-live ──► Focus first invalid field
💻 Interactive Code Playground
Starter Code
Line-by-Line Code Breakdown
- Line 87 (
aria-label="Tenant Provisioning Steps"): Establishes an accessible navigation landmark for the step progress tracker. - Line 89 (
aria-current="step"): WAI-ARIA 1.2 attribute indicating to assistive technology that Step 1 is the currently active step. - Line 99 (
<form id="wizard-form" novalidate>): Usesnovalidateto suppress automatic browser form submission validation while retaining the programmatic Constraint Validation API. - Line 101 (
<fieldset id="step-1" aria-labelledby="step-1-title">): Grouping mechanism that encapsulates each onboarding phase into an isolated, labeled semantic fieldset. - Line 169 (
input.checkValidity()): Evaluates the input against attributes (required,minlength="3",type="email"). - Line 170 (
input.reportValidity()): Focuses the invalid field and triggers the native error balloon tooltip.
Expected Browser Render Output
+----------------------------------------------------------------------------------------------------+
| (1) Organization O (2) Cluster Specs O (3) Confirmation |
+----------------------------------------------------------------------------------------------------+
| STEP 1: ORGANIZATION DETAILS |
| |
| Organization / Company Name * |
| [ Acme Global Inc. ] |
| |
| Administrator Work Email * |
| [ [email protected] ] |
| |
| ------------------------------------------------------------------------------------------------- |
| [ Continue to Step 2 ] |
+----------------------------------------------------------------------------------------------------+🏋️ Hands-On Exercise
🎯 The Challenge: Custom Domain Suffix Validation
Add custom business logic to Step 1 using setCustomValidity(). The administrator email MUST NOT be a public consumer address (gmail.com, yahoo.com, hotmail.com).
Instructions:
- Listen to the
inputevent on#admin-email. - Extract the domain suffix from the entered email address.
- If the domain is
gmail.comoryahoo.com, invokeemailInput.setCustomValidity("Please provide a corporate work email domain."). - Otherwise, clear the custom error by calling
emailInput.setCustomValidity("").
🏁 Starter Code Sandbox
⚠️ Common Pitfalls
- Forgetting to Clear
setCustomValidity(""): Once you set a custom validation message withsetCustomValidity('error'), the input will remain permanently invalid forever until you explicitly callsetCustomValidity(''). - Using
display: nonewithouthiddenon Fieldsets: Using custom CSS classes instead of the nativehiddenattribute or<fieldset disabled>can leave hidden form fields focusable by screen reader virtual cursors. - Missing
aria-current="step"on Trackers: Omittingaria-current="step"leaves screen reader users unaware of which step they are actively completing.
💡 Pro Tips
- Automatic Form State Restoration: Serialize valid wizard step states to
sessionStorageon step change so if a user accidentally refreshes their browser, they resume right where they left off. - Immediate Error Announcements with
aria-describedby: Associate custom error message containers with inputs via<input aria-describedby="email-error">to provide permanent inline error context.
📌 Key Takeaways
- Multi-step wizards should encapsulate each logical phase in a semantic
<fieldset>with an explicit<legend>. - Step indicators must be marked up using an ordered list
<ol>witharia-current="step"on the active step. - The HTML5 Constraint Validation API provides programmatic validation via
checkValidity()andreportValidity(). - Custom validation logic integrates into native browser bubbles using
setCustomValidity(message). - Always move keyboard focus to the first interactive element of the newly revealed
<fieldset>. - --