Skip to content

Stepper

<Stepper> is the controller for multi-step linear flows. Renders the indicator at the top, body for the active step, and exposes useStepper() to drive Next / Back from inside the body. For branching flows, use a state machine plus your own UI — Stepper is intentionally linear.

import from "@na/ui/components/Stepper" ▶ Open in Storybook packages/ui/src/components/Stepper.tsx

When to use

  • Onboarding flows (“Welcome”, “Pick a plan”, “Invite team”).
  • “Create agent” wizards.
  • Any 3–6 step linear-progression UI.

Don’t use Stepper for: branching flows (use a state machine), single-step forms (just render the form), or flows where the user should be able to skip arbitrary steps (use Tabs instead).

Default

Give the agent a name and a one-sentence purpose.

function Footer() {
const { back, next, isFirst, isLast } = useStepper();
return (
<div className="flex justify-between">
<Button variant="outline" onClick={back} disabled={isFirst}>Back</Button>
<Button onClick={next} disabled={isLast}>{isLast ? 'Submit' : 'Next'}</Button>
</div>
);
}
<Stepper
steps={[
{ id: 'basics', label: 'Basics' },
{ id: 'tools', label: 'Tools' },
{ id: 'review', label: 'Review' },
]}
defaultActive="basics"
>
<StepperHeader />
<StepperContent step="basics"></StepperContent>
<StepperContent step="tools"></StepperContent>
<StepperContent step="review"></StepperContent>
<Footer />
</Stepper>

API

Stepper

Prop Type Default Description
steps * StepDescriptor[] Each: { id, label, description?, disabled? }.
active string Controlled active step id. Pair with onActiveChange.
defaultActive string Initial active step id (uncontrolled).
onActiveChange (id: string) => void Fires when the active step changes.
allowJumpBack boolean true Whether visited steps are clickable in the header to go back.
orientation "horizontal" | "vertical" "horizontal" Visual orientation.

StepperHeader / StepperContent

StepperHeader renders the indicator with all steps. StepperContent requires a step prop matching one of the descriptor ids; it only renders when its step is active.

useStepper()

Returns { activeIndex, activeStep, isFirst, isLast, next, back, goto(id) }.

Design guidelines

✓ Do

  • Validate the current step before calling next() — you own the gate.
  • Surface progress in the header. Visual progress is a contract; users abandon flows without it.
  • Pre-fill defaults for "Review" steps; nobody likes re-typing what they just confirmed.

✗ Don't

  • Skip steps in linear order. Use a state machine + your own UI for branches.
  • Allow forward jumps to unvisited steps. The forward gate exists to enforce validation.
  • Hide the Back button on step 2. Going back is the most-used affordance after Next.

Accessibility

  • Header is role="tablist"; each step trigger is role="tab" with aria-selected.
  • Disabled / unreachable steps set aria-disabled and tabIndex={-1}.
  • Content panels are role="tabpanel" linked to their tab.
  • Tabs — When the user can switch freely.
  • Form — Inside each step.

▶ Open Stepper stories in Storybook