What is Stepper (multi-step form)?
Stepper (multi-step form): A stepper splits a long form or process into ordered steps and shows progress through them, with one step's fields visible at a time.
A stepper, also called a wizard or multi-step form, breaks a long task into a sequence of smaller screens. A progress indicator at the top shows the steps, which ones are complete and which one is current, and Back and Continue buttons move between them. Signup flows, checkouts, onboarding and setup assistants use steppers because a short form per screen feels manageable and lets each step adapt to earlier answers.
When to use it
- Long forms with natural groups, such as account, workspace and billing details.
- Dependent steps, where step three depends on what was chosen in step one.
- Not for short forms of five or six fields. Splitting them adds clicks and hides the total effort. Not for settings that people edit in any order; use tabs or sections.
Anatomy
- A step indicator with labels, usually three to five steps.
- The current step's heading and fields.
- Back and Continue actions, with Continue becoming Submit or Finish on the last step.
- Optionally, a review step that summarizes every answer with links to edit each section.
Label steps by what the user provides, like Account or Billing, not by number alone. Mark optional steps as optional. Completed steps can be links back for editing, while future steps stay inactive until reached, so the order stays intact.
Accessibility
Render the indicator as an ordered list and mark the current step with aria-current="step", with text such as "Step 2 of 4" rather than color alone. When the step changes, the old fields disappear, so move focus to the new step's heading (give it tabIndex={-1}) and screen reader users hear where they are. Validate on Continue, show errors next to the fields, and put focus on the first invalid field. Do not use the ARIA tabs pattern for a linear stepper; tabs imply free movement between panels.
How to build one in React
"use client"
import * as React from "react"
import { Stepper } from "@/components/ui/stepper"
import { Button } from "@/components/ui/button"
const STEPS = ["Account", "Workspace", "Invite"]
export function SignupWizard() {
const [step, setStep] = React.useState(0)
const heading = React.useRef<HTMLHeadingElement>(null)
function go(n: number) {
setStep(n)
requestAnimationFrame(() => heading.current?.focus())
}
return (
<form onSubmit={(e) => { e.preventDefault(); if (step < STEPS.length - 1) go(step + 1) }}>
<Stepper steps={STEPS} current={step} />
<h2 ref={heading} tabIndex={-1} className="mt-6 text-lg font-medium">
Step {step + 1} of {STEPS.length}: {STEPS[step]}
</h2>
{/* fields for the current step */}
<div className="mt-6 flex justify-between">
<Button type="button" variant="outline" disabled={step === 0} onClick={() => go(step - 1)}>Back</Button>
<Button type="submit">{step === STEPS.length - 1 ? "Finish" : "Continue"}</Button>
</div>
</form>
)
}
Keep every step's values in one form state so Back never loses input, and persist the draft to sessionStorage or the server for long flows. MiniDev provides the Stepper indicator, StepProgress, and complete flows in FormWizard and OnboardingWizard, all in the workflow category. On the last step, disable Finish only while the request is in flight, and if the server rejects an answer from an earlier step, send the user back to that step with the error shown next to the field.