Stepper
The Stepper component provides a vertical progress indicator for multi-step workflows like wizards and onboarding flows. It supports dynamic substeps, multiple visual states, transition animations, navigation guards, and keyboard navigation.
It is composed of Stepper.Root, Stepper.Step, and Stepper.SubStep to offer a structured and customizable interface.
Usage
import { Stepper } from "@harnessio/ui/components";
//...
const [currentStep, setCurrentStep] = useState("step-1");
return ( <Stepper.Root value={currentStep} onValueChange={setCurrentStep} title="Setup"> <Stepper.Step value="step-1" title="First Step" description="Do something" /> <Stepper.Step value="step-2" title="Second Step" description="Do another thing" /> <Stepper.Step value="step-3" title="Final Step" description="All done" /> </Stepper.Root>)Anatomy
All parts of the Stepper component can be imported and composed as required.
<Stepper.Root> <Stepper.Step> <Stepper.SubStep /> <Stepper.SubStep /> </Stepper.Step> <Stepper.Step /></Stepper.Root>Step States
Steps display different visual states based on their position relative to the active step and explicit state overrides.
Loading and Blocking
Steps can show a loading spinner and optionally block forward navigation while an async operation is in progress.
SubSteps
Steps can contain dynamic substeps that appear when the parent step is active. SubSteps support conditional rendering based on application state.
Indeterminate SubSteps
When a step has hasSubSteps but no children are provided, a placeholder indicator with ”…” is shown, signaling that substeps will be determined dynamically (e.g., after an API call resolves).
Here’s an example combining real substeps (.1, .2, .3) on the active step with an indeterminate placeholder on a following step:
Navigation Guard
Use the onBeforeChange prop to confirm navigation (e.g., warn users about losing progress when going back).
Completed State
When all steps are done, pass completed to show a “Complete” badge instead of the step counter.
Without Connectors
Set showConnectors={false} to hide the vertical connector lines between steps.
Skeleton Loading
When no children are passed, the stepper renders a skeleton placeholder. Control the number of skeleton items with skeletonCount.
API Reference
Root
The root container that manages stepper state, provides context to children, and renders the header with title and progress counter.
<Stepper.Root value={currentStep} // active step or substep value onValueChange={setStep} // callback when step changes title="Setup" // [OPTIONAL] displayed in the header completed={false} // [OPTIONAL] shows "Complete" instead of counter showConnectors={true} // [OPTIONAL] show connector lines between steps onBeforeChange={guardFn} // [OPTIONAL] navigation guard skeletonCount={3} // [OPTIONAL] skeleton items when no children className="my-class" // [OPTIONAL] custom class on the nav element> {/* Step components */}</Stepper.Root>Prop | Required | Default | Type |
|---|---|---|---|
| value | true | string | |
| onValueChange | true | (value: string) => void | |
| title | false | string | |
| completed | false | false | boolean |
| showConnectors | false | true | boolean |
| onBeforeChange | false | (from: string, to: string) => boolean | string | |
| skeletonCount | false | 3 | number |
| className | false | string | |
| children | false | ReactNode |
Step
An individual step in the stepper. Renders the indicator, title, description, connector, and optional substeps.
<Stepper.Step value="connect" // unique identifier for this step title="Connect" // step title (string or ReactNode) description="Choose provider" // [OPTIONAL] description text state="error" // [OPTIONAL] explicit state override loading={false} // [OPTIONAL] show spinner on indicator blocking={false} // [OPTIONAL] block forward navigation disabled={false} // [OPTIONAL] prevent interaction hasSubSteps={false} // [OPTIONAL] reserve substep space className="my-class" // [OPTIONAL] custom class> {/* Optional Stepper.SubStep children */}</Stepper.Step>Prop | Required | Default | Type |
|---|---|---|---|
| value | true | string | |
| title | true | string | ReactNode | |
| description | false | string | |
| state | false | 'error' | 'skipped' | |
| loading | false | false | boolean |
| blocking | false | false | boolean |
| disabled | false | false | boolean |
| hasSubSteps | false | false | boolean |
| className | false | string | |
| children | false | ReactNode |
SubStep
A substep nested within a parent step. Only rendered when the parent step is active.
<Stepper.SubStep value="auth" // unique identifier for this substep title="Authenticate" // substep title description="OAuth with GitHub" // [OPTIONAL] description text state="error" // [OPTIONAL] explicit state override disabled={false} // [OPTIONAL] prevent interaction className="my-class" // [OPTIONAL] custom class/>Prop | Required | Default | Type |
|---|---|---|---|
| value | true | string | |
| title | true | string | ReactNode | |
| description | false | string | |
| state | false | 'error' | 'skipped' | |
| disabled | false | false | boolean |
| className | false | string |
Accessibility
The stepper implements the following accessibility features:
- ARIA: Uses
aria-current="step"on the active step,aria-labelon each step button with position context, andaria-disabledfor disabled steps - Live Region: Announces step changes via
aria-live="polite"for screen readers - Keyboard Navigation: Supports ArrowDown, ArrowUp, Home, and End keys with roving tabindex
- Reduced Motion: Respects
prefers-reduced-motionby disabling transition animations
Best Practices
- Unique Values: Every step and substep must have a unique
valueacross the entire stepper - Controlled State: The component is always controlled — provide both
valueandonValueChange - Blocking Steps: Use
blockingfor async operations that must complete before proceeding (e.g., connectivity checks) - Navigation Guards: Use
onBeforeChangeto warn about data loss, not to enforce linear progression - SubStep Cleanup: When substeps unmount (e.g., provider changes), the stepper automatically falls back to the parent step value
- Skeleton State: Pass no children to show a skeleton — useful while step configuration loads asynchronously