TL;DR
A stepper component should do more than highlight the next number. For a dependable multi-step form, define each step’s identity and requirements, keep completion separate from the current position, validate before forward movement, and make back navigation preserve entered values. Show progress in text as well as visually, and test skipped, invalid, restored, and narrow-screen states.
A stepper component displays a workflow as a sequence of steps and shows where someone is in that sequence. In this guide, “stepper” means a progress and navigation control for a multi-step task, not the plus-and-minus control for adjusting a number. We will plan a reusable Vue-oriented control for a form whose fields can be generated from a schema. The stepper coordinates movement between sections; the form remains responsible for field values and validation.
The finished pattern will let someone enter workspace details, optionally invite a teammate, review the data, and submit it. We use that small example to make the rules testable, not to prescribe an onboarding flow for every product.
Table of contents
- Before you start
- 1. Choose a stepper interaction model
- 2. Define the step contract and states
- 3. Make every transition explicit
- 4. Validate at the boundary, then help users recover
- 5. Adapt the layout without changing the meaning
- 6. Give the indicator honest semantics and predictable focus
- 7. Wire the contract into Vue and document its limits
- 8. Test the workflow, not only its appearance
- Frequently asked questions
- Put the contract to work
- Sources
- Recommended Reads
Before you start
We need a Vue 3 application or another UI host, a definition of the form sections, a validator that can check the fields belonging to one section, and a final server endpoint that validates the whole submission. Decide where draft values will live and who can restore them. If a step depends on a server check, specify what happens when that request fails or is still pending. Do not make an optional step depend on an unavailable service without offering a recovery path.
Keep three inputs separate: the step definition describes labels and policy; the form model holds entered values; runtime workflow state records the active step, errors, and completed or skipped steps. Our schema-driven forms guide covers generating and validating fields; here we concentrate on the navigation boundary between those fields.
1. Choose a stepper interaction model
Start by asking whether the task actually has a meaningful order. Use a linear stepper when later sections depend on answers in earlier sections, such as an invite step that requires a created workspace. Use a non-linear stepper when revisiting any section is safe and the product can clearly mark unfinished sections. In either case, distinguish progress display from navigation permission. An indicator can describe an upcoming step without making it clickable.
A single-page form is usually clearer when there are only a few related fields and no meaningful transition. Tabs are a better fit for switching between peer sections that do not represent progress; tabs bring a distinct keyboard and selection pattern. For a flow with a changing number of stages, a text counter or progress indicator may be more honest than a fixed row of numbered steps. The W3C’s multi-page forms guidance recommends splitting long forms into logical groups and indicating progress. The WAI tabs pattern describes a different interaction model with tab-specific semantics and arrow-key navigation.
For our example, choose linear forward movement with editable completed steps. Users may go back at any time, but an unfinished future step cannot be opened directly. Check: with the current step set to Workspace, clicking Review should not change the active step; clicking Back from Invites should return to Workspace with its values intact.
2. Define the step contract and states
Give each step a stable ID, a short visible label, a relationship to the form section it renders, an optionality flag, and a rule that determines whether a user may enter it. Keep runtime statuses out of the authored definition: a step can be configured as optional without being currently skipped, and a completed step can later become invalid after an earlier answer changes.
Here is illustrative application configuration, not a DOM Studio or Vue library API:
{
"steps": [
{ "id": "workspace", "label": "Workspace", "section": "workspace", "optional": false },
{ "id": "invites", "label": "Invites", "section": "invites", "optional": true },
{ "id": "review", "label": "Review", "section": "review", "optional": false }
],
"initialStepId": "workspace"
}
The application separately tracks activeStepId, values, completedStepIds, skippedStepIds, and validation results keyed by step ID. Derive what the indicator shows rather than treating position as proof of completion. In particular, an optional step bypassed on the way to Review should appear skipped, not completed. Material UI’s stepper documentation makes the same distinction for optional steps: passing a step does not automatically mean it was completed. That is a useful conceptual reference, not an API recommendation for Vue.
| Visible state | Meaning | Navigation implication |
|---|---|---|
| Current | This section’s content is open | Its fields are available to edit |
| Completed | Its required checks last passed | It can be revisited; recheck after relevant edits |
| Upcoming | Not reached yet | Follow the chosen linear or non-linear policy |
| Disabled | Cannot be entered under current conditions | Explain why when a user needs to act |
| Skipped | An optional section was deliberately bypassed | Do not claim its data was supplied |
| Error | A check failed or a server returned a problem | Provide a path back to the relevant fields |
Error and current can coexist: if Continue fails on Invites, Invites remains current and shows an error. Choose a documented visual precedence so a red error marker does not erase the current-position cue. Check: add an error to the active section and confirm both its label and error message remain understandable without color.

3. Make every transition explicit
Write the rules before binding buttons. On Next, persist current values to the draft, run the current step’s checks, and move only if those checks pass. On Back, retain values and move without treating the action as submission. A direct click on a completed step should reopen that section; clicking a future step should either follow the non-linear policy or leave the user in place with a clear explanation. Give an optional step a visible Skip action, and record that decision instead of pretending its fields passed validation.
There are two distinct gates. The navigation gate determines whether the current transition is allowed. The submission gate checks all applicable required sections and server rules before committing the workflow. A Review step should list answers, mark missing items, and link back to editable sections; it is not proof that the previous checks can never change. If editing Workspace invalidates a dependent choice in Invites, clear its completed state and ask the user to review it again.
Prevent overlapping transitions: while an asynchronous validator or save is running, do not let repeated Continue clicks race to advance twice. If validation fails or times out, remain on the same step, preserve values, show a retry path, and clear any stale success state. The final submit action should be idempotent or protected against duplicate requests on the server; a disabled button alone is not a server guarantee.
Check: run Next twice quickly while validation is pending, then reject the validation request. The workflow must not advance. Edit a dependency on a completed step and confirm Review no longer claims everything is ready.
4. Validate at the boundary, then help users recover
Validate the current section on forward movement, not every future field the user has not seen. On Back, preserve what was entered and defer a fresh validation check until the user attempts another forward transition or submits. On final submission, recheck every applicable section and let the server reject stale, unauthorized, or conflicting data. Return server errors to the correct section and field, then open the earliest affected section when appropriate.
When a transition fails, put an error summary near the section heading and link each error to its field. Move focus to the summary or first invalid field consistently, and connect the field to a specific explanation. The W3C form notification tutorial recommends understandable correction instructions, a summary with field links, and inline feedback. Avoid an error badge on the step label with no explanation in the content.
If optional Invites contains a malformed address, make the choice explicit: either fix the address before continuing or Skip Invites, which discards or parks the invalid invite draft according to your product policy. Never silently send invalid draft data just because that section is optional. Check: enter an invalid address, press Continue, and verify the step does not change; use Skip and verify the final payload follows the documented optional-step rule.
For a framework-independent walkthrough of a multi-step form with validation and focus handling, this video provides a useful implementation companion. Adapt its interaction ideas to your own schema and step policy rather than copying its application code unchanged.
5. Adapt the layout without changing the meaning
A horizontal stepper works when labels are short and the available width is generous. A vertical list gives longer labels room, and on a phone a compact “Step 2 of 3: Invites” counter can be clearer than squeezing three tiny circles into one row. Material UI documents horizontal, vertical, and compact mobile treatments and warns against long labels in a horizontal stepper. Choose the treatment based on content and width, not device name alone.
Keep the step IDs, statuses, label text, and active content the same across breakpoints. If labels collapse visually, keep a visible step name near the form heading and make the full name available to assistive technology. Do not rely on icons, connector lines, or color to communicate that a section has an error or was skipped. Test large text and narrow containers as well as a standard desktop width.
Check: resize from wide to narrow midway through Invites. The same step remains active, values stay intact, the current label remains readable, and keyboard focus does not jump to an unrelated control.

6. Give the indicator honest semantics and predictable focus
Render the sequence as an ordered list with a label for each step. Use a real link for a completed step that changes the URL, or a real button for an in-page step change. A future step that is not actionable can be plain text; avoid styling it like an enabled button. Mark exactly one current item with aria-current="step". W3C’s multi-page form tutorial demonstrates an ordered step list, while its aria-current technique explicitly includes the step value.
For an in-page flow, ensure Tab reaches each actionable step and Continue/Back in logical order; native buttons already support Enter and Space. Do not add role="tablist" just because the indicator is horizontal. That role would promise the tab keyboard behavior and relationships in the WAI tabs pattern, which this workflow does not implement. When the visible section changes, move focus to its heading or first meaningful control after the new content renders. Announce a concise “Step 2 of 3: Invites” update if the heading and focus change alone do not provide sufficient feedback; avoid repeatedly announcing the same message through multiple live regions.
When validation blocks movement, keep focus on a discoverable error summary or invalid control instead of placing it on a now-inactive indicator. Check: complete the flow with keyboard only, then repeat with a screen reader. Confirm the current step, any skipped or invalid step, and the reason forward movement is blocked can be found without relying on color.
7. Wire the contract into Vue and document its limits
In a Vue 3 single-file component, use ref() for the active step ID and draft model. Derive indicator states from those values and the latest validation results; do not update a second, competing “progress percentage” manually. Render steps from stable IDs, and treat the form renderer and validator as application-supplied dependencies, not built-in stepper capabilities. Vue’s reactivity guide documents ref() and nextTick(): after changing the active ID, wait for the new DOM update before moving focus to its heading. Do not start the next transition until the pending validation resolves.
If your form generator mounts only the active section, keep entered values in an owner above that section or in a persistent draft store. Restoring a draft must validate the saved step ID against the current definition and recheck dependent completion flags; a renamed or removed step should not strand the user. For a Web Components implementation, preserve the same state contract and emit a meaningful transition request rather than allowing a visual shell to make unsupported validation decisions.
Document the control’s inputs, transition events, focus behavior, allowed states, and validation ownership near its source. Our Vue component metadata reference describes how props, events, accessibility notes, and source hints can make a component inspectable. Those details also help an AI-assisted workflow understand what a stepper may do without inventing a navigation API. DOM Studio’s workspace setup wizard example shows a concrete multi-step interface; treat it as an application block to adapt, not as proof of a ready-made, schema-driven stepper API.
Check: restore an older draft after renaming a step. The application either maps that ID deliberately or safely falls back to the first available section and prompts for review. No completed marker should survive a changed validation dependency by accident.
8. Test the workflow, not only its appearance
Write tests around transitions and payloads rather than a screenshot of the default state. Start with this release matrix:
- Normal path: each Next action moves exactly one step, and Review shows the entered values.
- Invalid and delayed checks: forward movement stays blocked, double clicks do not race, and recovery preserves the draft.
- Back and direct navigation: values persist; completed steps can reopen; inaccessible future steps cannot be entered in a linear flow.
- Optional and dependent sections: Skip is distinct from Complete, and changing an earlier answer invalidates dependent completion.
- Submit and restore: server errors reopen the right section; stale step IDs fall back safely; duplicate submissions do not create duplicate effects.
- Accessibility and layout: the current step is announced, error recovery works by keyboard, and narrow or zoomed layouts keep labels and focus usable.
If a scenario fails, identify whether the defect belongs to the step policy, form validator, rendering layer, or persistence adapter. Expected result: we can change the layout or form fields without silently changing how Next, Skip, Back, and Submit behave.
Frequently asked questions
Is a stepper component the same as a progress bar?
No. A progress bar can communicate how far someone has gone, but a workflow stepper also names stages and may allow navigation. Use a simple counter or progress indicator when the names or direct-step actions add no value.
Should users be able to click every step?
Only when the workflow supports it. In a dependent form, completed steps can be editable while upcoming steps remain non-interactive. In a non-linear workflow, clicking ahead is reasonable if the product can represent unfinished and invalid sections clearly before submission.
Put the contract to work
We now have a stepper design whose status, navigation, and validation rules can be checked independently of its appearance. Build the smallest three-step flow using your existing form sections, then test the invalid, skipped, restored, and server-rejected paths before reusing the control. If your team works in Vue, start from DOM Studio’s onboarding block for an interface example and adapt its progression rules to the explicit contract above.
Sources
- W3C WAI: Multi-page Forms
- W3C WAI: User Notification
- W3C WAI: Using aria-current to identify the current item
- W3C WAI: Tabs Pattern
- Vue: Reactivity Fundamentals
- Material UI: Stepper
- DOM Studio: Workspace setup wizard
- DOM Studio: Vue Component Metadata
- DOM Studio: Schema-Driven Forms
