1
ComponentsNavigation

Stepper

Read-only or interactive ordered workflow progress with open, current, done and blocked states.

Purpose

Use Stepper to communicate ordered workflow progress or let users navigate between known steps.

Process steps

  1. Plan
    Requirements accepted
  2. Build
    Docs examples complete
  3. 3
    Review
    Browser and source audit
  4. 4
    Ship
    Publish when accepted
import { Stepper, StepperItem } from "@/components/ui/stepper";

export function Example() {
  const steps = [
    { title: "Plan", status: "done" },
    { title: "Build", status: "done" },
    { title: "Review", status: "current" },
    { title: "Ship", status: "open" },
  ] as const;

  return (
    <Stepper>
      {steps.map((step, index) => (
        <StepperItem
          index={index + 1}
          key={step.title}
          status={step.status}
          title={step.title}
        />
      ))}
    </Stepper>
  );
}

Installation

With the Siteplane registry alias configured, add the component and import the installed source from your app.

CLI

pnpm dlx shadcn@4.16.1 add @siteplane/stepper

Import

import { Stepper, StepperButtonItem, StepperItem } from "@/components/ui/stepper";

Need the machine-readable source metadata? View registry JSON.

Usage

Stepper shows read-only process state by default. Use StepperButtonItem only when steps are navigable.

import { Stepper, StepperItem } from "@/components/ui/stepper";

export function Example() {
  const steps = [
    { title: "Plan", status: "done" },
    { title: "Build", status: "done" },
    { title: "Review", status: "current" },
    { title: "Ship", status: "open" },
  ] as const;

  return (
    <Stepper>
      {steps.map((step, index) => (
        <StepperItem
          index={index + 1}
          key={step.title}
          status={step.status}
          title={step.title}
        />
      ))}
    </Stepper>
  );
}

Examples

Read-only progress, all indicator statuses and clickable steps are props of the same primitive.

Read-only progress

StepperItem is the non-interactive variant for fixed status displays.

  1. Connect
    Repository connected and verified.
  2. 2
    Build
    Docs examples are being written.
  3. 3
    Review
    Waiting for the review pass.
<Stepper>
  <StepperItem
    description="Repository connected and verified."
    status="done"
    title="Connect"
  />
  <StepperItem
    description="Docs examples are being written."
    index={2}
    status="current"
    title="Build"
  />
  <StepperItem
    description="Waiting for the review pass."
    index={3}
    title="Review"
  />
</Stepper>

Statuses

done renders a check, blocked renders a lock with a dashed warning ring.

  1. 1
    Open
    Not started yet.
  2. 2
    Current
    In progress right now.
  3. Done
    Completed and verified.
  4. Blocked
    Waiting on approval.
<Stepper>
  <StepperItem description="Not started yet." index={1} title="Open" />
  <StepperItem description="In progress right now." index={2} status="current" title="Current" />
  <StepperItem description="Completed and verified." status="done" title="Done" />
  <StepperItem description="Waiting on approval." status="blocked" title="Blocked" />
</Stepper>

Clickable steps

current moves only the selection ring; status icons stay fixed, and disabled steps remain unreachable.

const [activeStep, setActiveStep] = useState("profile");

<Stepper aria-label="Onboarding steps">
  {onboardingSteps.map((step, index) => (
    <StepperButtonItem
      current={step.value === activeStep}
      description={step.description}
      disabled={step.status === "blocked"}
      index={index + 1}
      key={step.value}
      onClick={() => setActiveStep(step.value)}
      status={step.status}
      title={step.title}
    />
  ))}
</Stepper>

API Reference

The reference lists the source-owned exports and props that are easy to miss in visual examples. Inherited Base UI props remain available unless the wrapper narrows them.

  • Stepper renders the ordered-list root.
  • StepperItem is read-only; StepperButtonItem renders an interactive step button. Both accept status="open" | "current" | "done" | "blocked", title, description, index and children.
  • children render below the title and description inside the content slot, so a step can include supporting fields, actions or status details.
  • StepperButtonItem accepts normal button props including disabled; current sets aria-current="step".
  • current controls only the selected indicator ring. It does not mutate status, so checkmarks, numbers and locks remain stable when users navigate.

Motion

Component motion uses the shared Siteplane motion contract. See the global motion guide for provider setup, tokens and reduced-motion behavior.

  • Indicator state uses the shared fast transition for its small focus/status accent.
  • Step content does not mount or collapse automatically. The operating-system reduced-motion preference removes the decorative indicator transition; the forced reducedMotionPreset shortens its shared fast duration from 140ms to 60ms.

Accessibility

  • Keep visual and DOM order identical and preserve the ordered-list structure.
  • Mark exactly one current step when the workflow has a current position.
  • Use StepperButtonItem only when clicking a step performs a real navigation or state change; disabled steps must remain clearly unavailable.

Implementation Guidance

  • Use Timeline for read-only history and Stepper for ordered workflow progress.
  • Put supporting content in the documented children slot instead of creating a parallel item wrapper.
  • Do not make a read-only StepperItem appear clickable.

On This Page