component
Stepper
BetaA composable primitive for building accessible multi-step flow architecture with local source ownership.
Examples
Start with one production-shaped flow before reaching for adapters or recipes.
Checkout flow
Checkout flow
PrimitiveA compact preview that starts at the cart and advances one step at a time.
Installation
Add the primitive through the shadcn registry. The command copies the source into your app.
pnpm dlx shadcn@latest add @stepper/stepperUsage
Import the primitive parts you need, then compose the list, items, and controls directly in JSX.
import {
Stepper,
StepperContent,
StepperDescription,
StepperIndicator,
StepperItem,
StepperLabel,
StepperList,
StepperNext,
StepperPrevious,
StepperTrigger,
useStepperItem,
} from "@/components/ui/stepper";import * as React from "react";
import { Check, CreditCard, ShoppingCart, Truck } from "lucide-react";
import {
Stepper,
StepperContent,
StepperDescription,
StepperIndicator,
StepperItem,
StepperLabel,
StepperList,
StepperNext,
StepperPrevious,
StepperTrigger,
useStepperItem,
} from "@/components/ui/stepper";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
type StepValue = "cart" | "shipping" | "payment";
export function CheckoutFlow() {
const [value, setValue] = React.useState<StepValue>("cart");
const [showLabels, setShowLabels] = React.useState(true);
return (
<Stepper value={value} onValueChange={(next) => setValue(next as StepValue)}>
<StepperList>
<CheckoutStep
value="cart"
title="Cart"
description="Review items"
icon={ShoppingCart}
showLabels={showLabels}
/>
<CheckoutStep
value="shipping"
title="Shipping"
description="Delivery details"
icon={Truck}
showLabels={showLabels}
/>
<CheckoutStep
value="payment"
title="Payment"
description="Secure checkout"
icon={CreditCard}
showLabels={showLabels}
/>
</StepperList>
<StepperContent value="cart">Review selected products.</StepperContent>
<StepperContent value="shipping">Confirm delivery details.</StepperContent>
<StepperContent value="payment">Choose a payment method.</StepperContent>
<div className="mt-6 flex justify-between">
<StepperPrevious />
{value === "payment" ? (
<Button type="button">Place order</Button>
) : (
<StepperNext />
)}
</div>
<label className="mt-4 flex items-center justify-center gap-2 border-t pt-4">
<Checkbox
checked={showLabels}
onCheckedChange={(checked) => setShowLabels(checked === true)}
/>
Show labels
</label>
</Stepper>
);
}
function CheckoutStep({
value,
title,
description,
icon: Icon,
showLabels,
}: {
value: StepValue;
title: string;
description: string;
icon: React.ComponentType<{ className?: string }>;
showLabels: boolean;
}) {
const { stepPosition, stepState } = useStepperItem<StepValue>();
const isComplete = stepState === "completed" || stepPosition === "previous";
return (
<StepperItem value={value} defaultTrigger={false}>
<StepperTrigger>
<StepperIndicator>
{isComplete ? <Check /> : <Icon />}
</StepperIndicator>
<span className="relative block min-h-10 min-w-0">
<span
className={
showLabels
? "flex min-w-0 flex-col gap-1"
: "invisible flex min-w-0 flex-col gap-1 opacity-0"
}
>
<StepperLabel>{title}</StepperLabel>
<StepperDescription>{description}</StepperDescription>
</span>
</span>
</StepperTrigger>
</StepperItem>
);
}Files
The registry installs a single editable primitive into your local component folder.
Composition
Use the following composition to build a Stepper.
Stepper
├── StepperList
│ ├── StepperItem
│ │ ├── StepperTrigger
│ │ │ ├── StepperIndicator
│ │ │ ├── StepperLabel
│ │ │ └── StepperDescription
│ │ └── StepperSeparator
│ └── StepperItem
├── StepperContent
├── StepperPrevious
└── StepperNextOrder model
Composed mode registers StepperItem primitives in mount order for simple JSX flows. Pass steps when a flow is dynamic, conditional, routed, or rendered from data so the order stays explicit.
Headless composition
Hide the visual list and drive custom controls with useStepper when the product surface needs a different layout.
Stepper
├── StepperList className="sr-only"
│ └── StepperItem
├── CustomStepNavigation
├── StepperContent
└── CustomFooter using useStepper()API Reference
The public surface stays small: root state, ordered list, item state, trigger composition, content panels, and navigation helpers.
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| Prop | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | false | Render StepperPrevious or StepperNext onto a custom button primitive with Radix Slot. |
| disabled | boolean | false | Disables the navigation button in addition to canGoPrevious or canGoNext. |
| onBeforePrevious | () => boolean | Promise<boolean> | - | Runs before StepperPrevious changes the value. Return false to cancel navigation. |
| onBeforeNext | () => boolean | Promise<boolean> | - | Runs before StepperNext changes the value. Return false to cancel navigation. |