api
Stepper API
The V1 API keeps the surface small: one root, one ordered list, item primitives, content panels, and previous/next helpers.
Root props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | - | Controlled active step value. |
| defaultValue | string | - | Initial active step for uncontrolled usage. |
| onValueChange | (value: string) => void | - | Called when the selected step changes, including controlled fallbacks to the first enabled step. |
| orientation | "horizontal" | "vertical" | "horizontal" | Controls list layout and connector direction. |
| steps | { value: string; disabled?: boolean }[] | - | Optional explicit step list for headless, dynamic, or conditional flows. When provided, Stepper uses it instead of composed StepperItem registration order. |
Controlled fallback
Controlled values are still normalized to an enabled step so the UI never points at missing or disabled content.
- Missing value
- First enabled step
- When value points to a step that does not exist, Stepper renders the first enabled step and calls onValueChange with that fallback.
- Disabled value
- Skipped
- When the selected step becomes disabled, Stepper moves to the first enabled step so controls and content stay coherent.
- Real flows
- App derived
- For routes, forms, and server data, derive value from the app state instead of treating Stepper as the workflow owner.
List props
| Prop | Type | Default | Description |
|---|---|---|---|
| aria-label | string | "Progress steps" | Accessible name for the ordered list. Use aria-labelledby when the list has an external heading. |
Item props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | - | Unique step id used to connect trigger and content. |
| completed | boolean | false | Marks a step as completed and updates data-state. |
| defaultTrigger | boolean | true | Controls whether StepperItem renders the default trigger markup. Set it to false when composing your own StepperTrigger. |
| disabled | boolean | false | Disables the trigger and skips the step in next/previous navigation. |
| error | boolean | false | Marks a step as needing attention and exposes data-state="error". |
| separator | boolean | true | Controls the default connector separator after this item. Set to false when rendering a custom StepperSeparator. |
Trigger props
| Prop | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | false | Render the trigger props onto a custom button or link with Radix Slot. |
| disabled | boolean | false | Disables the trigger in addition to the parent StepperItem disabled state. With asChild, aria-disabled and tabIndex are applied instead of a native disabled attribute. |
asChild requirements
StepperTrigger and navigation helpers use Radix Slot composition. The child owns the final element, so it must accept the props Stepper passes.
- Focusable child
- button or link
- StepperTrigger asChild should render a focusable element that can receive keyboard focus.
- Prop forwarding
- className and aria-*
- Custom components must spread props so state attributes, classes, events, and ARIA props reach the real element.
- Refs and events
- forwardRef
- Forward refs for custom components and avoid preventDefault unless you intentionally block navigation.
Content props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | - | Step value this content panel belongs to. |
| forceMount | boolean | false | Keeps inactive content mounted and hidden. Prefer keepMounted for form persistence semantics. |
| keepMounted | boolean | false | Semantic alias for forceMount, useful when preserving form or adapter state between steps. |
| asChild | boolean | false | Render the content props onto a child element with Radix Slot. |
Accessibility model
Stepper uses progress-step semantics instead of tab semantics, so it can safely compose with forms, route links, and validation gates.
- Current step
- aria-current="step"
- The active trigger is announced as the current step in a related step list.
- Keyboard
- Arrows, Home, End
- Arrow keys move focus between enabled triggers; Enter and Space keep the native button/link activation model.
- Panels
- aria-labelledby
- StepperContent renders a region labelled by its trigger and can stay mounted with forceMount.
- Semantics
- Not tabs
- Stepper avoids tab roles because it can represent route changes, validation gates, or progress indicators.
useStepper hook
Use the public hook for custom footers, async validation controls, or buttons outside the visual list.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | undefined | - | Current active step after fallback resolution. |
| steps | { value: string; disabled: boolean }[] | - | Registered step order used by navigation helpers. |
| currentIndex | number | - | Zero-based index for the current active step, or -1 when no enabled step is active. |
| totalSteps | number | - | Total number of registered steps, including disabled steps. |
| setValue | (value: string) => void | - | Selects an enabled step by value. |
| canGoPrevious / canGoNext | boolean | - | Whether previous or next enabled steps exist. |
| goPrevious / goNext | () => void | - | Moves to the previous or next enabled step. |
wizard-footer.tsx
Copy code is available in the top right.
function WizardFooter() {
const {
canGoPrevious,
canGoNext,
currentIndex,
totalSteps,
goPrevious,
goNext,
} = useStepper();
return (
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">
Step {currentIndex + 1} of {totalSteps}
</span>
<div className="flex gap-2">
<Button type="button" variant="outline" disabled={!canGoPrevious} onClick={goPrevious}>
Back
</Button>
<Button type="button" disabled={!canGoNext} onClick={goNext}>
Continue
</Button>
</div>
</div>
);
}Primitive parts
Each part exposes data-slot and data-state attributes for predictable styling.
Composable trigger
composable-trigger.tsx
Copy code is available in the top right.
<StepperItem value="shipping" defaultTrigger={false}>
<StepperTrigger>
<StepperIndicator />
<span className="flex flex-col gap-1">
<StepperLabel>Shipping</StepperLabel>
<StepperDescription>Delivery address</StepperDescription>
</span>
</StepperTrigger>
</StepperItem>