Stepper
Named step actions and progress indicators for multi-step processes.
Powered by
Installation
npx shadcn@latest add @redpanda/stepperpnpm dlx shadcn@latest add @redpanda/stepperyarn dlx shadcn@latest add @redpanda/stepperbunx shadcn@latest add @redpanda/steppernubx shadcn@latest add @redpanda/stepperaube dlx shadcn@latest add @redpanda/stepperWhen to use
Steppers guide users through multi-step processes. Use this decision tree:
Usage
const { Stepper } = defineStepper( { id: 'step-1', title: 'Step 1', }, { id: 'step-2', title: 'Step 2', }, { id: 'step-3', title: 'Step 3', },);function CheckoutStepper() { const [navigationError, setNavigationError] = useState<string>(); const reportNavigationError = (reason: unknown) => { setNavigationError(reason instanceof Error ? reason.message : 'Could not change steps.'); };
return ( <> {navigationError && <p role="alert">{navigationError}</p>} <Stepper.Provider className="space-y-4" variant="horizontal"> {({ methods }) => ( <> <Stepper.Navigation aria-label="Checkout steps"> {methods.steps.map((step) => ( <Stepper.Step key={step.id} of={step.id} title={<Stepper.Title>{step.title}</Stepper.Title>} onClick={() => { methods.goTo(step.id).catch(reportNavigationError); }} /> ))} </Stepper.Navigation> <Stepper.Panel>{methods.current.title} content</Stepper.Panel> <Stepper.Controls> <Button disabled={methods.isFirst} onClick={() => { methods.prev().catch(reportNavigationError); }} variant="default" > Previous </Button> <Button onClick={() => { (methods.isLast ? methods.reset() : methods.next()).catch(reportNavigationError); }} > {methods.isLast ? 'Reset' : 'Next'} </Button> </Stepper.Controls> </> )} </Stepper.Provider> </> );}Anatomy and slots
Stepper.Navigation is a named navigation landmark containing an ordered list. Each Stepper.Step owns one list item and, in horizontal or vertical layouts, a native button. The button is named by its visible title; its numeral or icon is decorative.
titleis required. Pass text or aStepper.Titleelement, including its polymorphicrenderprop.descriptioncontains supplementary content, such asStepper.Descriptionor a statusBadge.panelcontains inline step content. All supplied content renders directly, including fragments and multiple panels. Keep active-step conditions explicit. Horizontal flows may instead render one externalStepper.Panelfrom the current step.Stepper.Stepdoes not accept marker children. Move them to these named props; there is no implicit child discovery.Stepper.Title,Description,PanelandControlsretain their Base UIrenderand native ref contracts.
<Stepper.Step of={step.id} title={<Stepper.Title render={<span />}>{step.title}</Stepper.Title>} description={<Stepper.Description>Configure this step</Stepper.Description>} panel={methods.is(step.id) ? <Stepper.Panel>Step content</Stepper.Panel> : null} onClick={() => { methods.goTo(step.id).catch(reportNavigationError); }}/>Keyboard and navigation
This is process navigation, not a tab widget. The current action exposes aria-current="step"; panels are ordinary content rather than artificial tabpanels. Tab and Shift+Tab visit enabled actions. Enter and Space invoke the native action. Arrow keys follow the layout: Left/Right horizontally, Up/Down vertically. Home and End focus the first and last eligible action. Focus movement stays inside its Provider and does not activate a step. Caller preventDefault() cancels the convenience key handling.
Use the action’s onClick to validate before moving, or supply Stepperize’s beforeStepChange guard. Rejected transitions must produce visible application feedback. linear makes policy-ineligible actions native disabled; a focused action never bypasses validation. Circle layout is a named, noninteractive progress indicator, with navigation provided by the flow’s controls.
Stepper.Step.variant selects a finite Button tone recipe; unstyled and null are not supported. The current-step halo and keyboard-focus ring remain separate owned states.
Sync the step with the URL
Pass the current step to Stepper.Provider and update the URL in onStepChange. The stepper then follows the URL, including the browser’s back and forward buttons, with no extra component:
const { step } = Route.useSearch();const navigate = Route.useNavigate();
<Stepper.Provider step={step} onStepChange={(next) => navigate({ search: (prev) => ({ ...prev, step: next }) })}> {/* ... */}</Stepper.Provider>;Examples
With description
With icon
With label orientation
Responsive
With tracking
With variants
With progress bar
With per-step status
Props
Credits
- We take our inspiration from Stepperize for the stepper component and style.