Skip to content
Redpanda UI
Esc
↑↓navigate↵open⌘Jpreview
Made by damianricobelli
Edit on GitHub

Stepper

Named step actions and progress indicators for multi-step processes.

Powered by

Installation

bash
npx shadcn@latest add @redpanda/stepper
bash
pnpm dlx shadcn@latest add @redpanda/stepper
bash
yarn dlx shadcn@latest add @redpanda/stepper
bash
bunx shadcn@latest add @redpanda/stepper
bash
nubx shadcn@latest add @redpanda/stepper
bash
aube dlx shadcn@latest add @redpanda/stepper

When to use

Steppers guide users through multi-step processes. Use this decision tree:

Usage

tsx
const { Stepper } = defineStepper(  {    id: 'step-1',    title: 'Step 1',  },  {    id: 'step-2',    title: 'Step 2',  },  {    id: 'step-3',    title: 'Step 3',  },);
tsx
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.

  • title is required. Pass text or a Stepper.Title element, including its polymorphic render prop.
  • description contains supplementary content, such as Stepper.Description or a status Badge.
  • panel contains inline step content. All supplied content renders directly, including fragments and multiple panels. Keep active-step conditions explicit. Horizontal flows may instead render one external Stepper.Panel from the current step.
  • Stepper.Step does not accept marker children. Move them to these named props; there is no implicit child discovery.
  • Stepper.Title, Description, Panel and Controls retain their Base UI render and native ref contracts.
tsx
<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:

tsx
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.

Recent changes

  • patchv1.2.0Pin shipped dependency floors to the version we develop against. Registry items now declare ranges like `^5.1.9` (the actual installed version) instead of collapsing to `^5.0.0`, so consumers start on the known-tested baseline while caret semantics still allow any compatible release within the same major.#133
  • minorv1.1.0Theme docs refresh, readability pass on semantic foregrounds, and consumer-facing Base UI regression fixes.#121
  • minorv1.0.0Post-Base-UI polish. Public API unchanged.#116
  • majorv1.0.0Migrate every Radix-based primitive to `@base-ui/react@^1.4.0` (Base UI).#114
  • minorv0.3.0Add theme-provider component to the registry with documentation and tests. Includes playground type improvements (export RegistryItem, remove as-const boilerplate) and docs site dark mode border color fix.#109
See full history →

Last updated on

Was this page helpful?