Stepper
Stepper Examples
Copy-ready examples with live previews.
Basic (static)
Use `value` to set the active step. Without `onChange`, it behaves as static.
Live preview
Basic static
import Stepper from "@/packages/ui/src/_components/stepper/Stepper";
export default function Example() {
return (
<Stepper
aria-label="Checkout steps"
value={1}
steps={[
{ id: "a", label: "Cart" },
{ id: "b", label: "Delivery" },
{ id: "c", label: "Payment" },
]}
/>
);
}Clickable (controlled)
Provide `onChange` to enable clickable behavior and update your state.
Live preview
Clickable (controlled)
"use client";
import { useState } from "react";
import Stepper from "@/packages/ui/src/_components/stepper/Stepper";
export default function Example() {
const [active, setActive] = useState(0);
return (
<Stepper
value={active}
onChange={(nextIndex) => setActive(nextIndex)}
steps={[
{ id: "a", label: "Account" },
{ id: "b", label: "Profile" },
{ id: "c", label: "Done" },
]}
/>
);
}Badges + support text + content panels
Each step can render a badge, a secondary line, and an optional content panel.
Live preview
Content panels (active)
"use client";
import { useState } from "react";
import Stepper from "@/packages/ui/src/_components/stepper/Stepper";
import Icon from "@/packages/ui/src/_components/icon/Icon";
export default function Example() {
const [active, setActive] = useState(0);
return (
<Stepper
value={active}
onChange={(next) => setActive(next)}
contentMode="active"
steps={[
{
id: "a",
badge: <Icon name="person" style="rounded" size="medium" variant="primary" fill={1} />,
label: "Account",
supportText: "Basic details",
content: <div>Content: Account</div>,
},
{
id: "b",
badge: <Icon name="home" style="rounded" size="medium" variant="secondary" fill={1} />,
label: "Address",
supportText: "Shipping & billing",
content: <div>Content: Address</div>,
},
]}
/>
);
}Orientation
Horizontal for wizards, vertical for side layouts and long labels.
Live preview
Horizontal + vertical
"use client";
import { useState } from "react";
import Stepper from "@/_components/stepper/Stepper";
export default function Example() {
const [active, setActive] = useState(0);
return (
<>
<Stepper
value={active}
onChange={(next) => setActive(next)}
orientation="horizontal"
aria-label="Horizontal stepper"
steps={[
{ id: "a", label: "Account" },
{ id: "b", label: "Address" },
{ id: "c", label: "Payment" },
]}
/>
<Stepper
value={active}
onChange={(next) => setActive(next)}
orientation="vertical"
aria-label="Vertical stepper"
steps={[
{ id: "a", label: "Account" },
{ id: "b", label: "Address" },
{ id: "c", label: "Payment" },
]}
/>
</>
);
}Size
Use `sm` for dense UI, `md` for default, and `lg` for emphasis.
Live preview
sm / md / lg
"use client";
import { useState } from "react";
import Stepper from "@/_components/stepper/Stepper";
export default function Example() {
const [active, setActive] = useState(0);
return (
<>
<Stepper value={active} onChange={setActive} size="sm" steps={[{ id: "a", label: "A" }, { id: "b", label: "B" }]} />
<Stepper value={active} onChange={setActive} size="md" steps={[{ id: "a", label: "A" }, { id: "b", label: "B" }]} />
<Stepper value={active} onChange={setActive} size="lg" steps={[{ id: "a", label: "A" }, { id: "b", label: "B" }]} />
</>
);
}Variant
Variants are semantic; your CSS defines the tones.
Live preview
All variants
import Stepper from "@/_components/stepper/Stepper";
export default function Example() {
return (
<>
<Stepper value={0} variant="primary" steps={[{ id: "a", label: "A" }, { id: "b", label: "B" }]} />
<Stepper value={0} variant="secondary" steps={[{ id: "a", label: "A" }, { id: "b", label: "B" }]} />
<Stepper value={0} variant="tertiary" steps={[{ id: "a", label: "A" }, { id: "b", label: "B" }]} />
<Stepper value={0} variant="success" steps={[{ id: "a", label: "A" }, { id: "b", label: "B" }]} />
<Stepper value={0} variant="warning" steps={[{ id: "a", label: "A" }, { id: "b", label: "B" }]} />
<Stepper value={0} variant="danger" steps={[{ id: "a", label: "A" }, { id: "b", label: "B" }]} />
<Stepper value={0} variant="info" steps={[{ id: "a", label: "A" }, { id: "b", label: "B" }]} />
</>
);
}Flow
`default` tries to fit; `scroll` enables horizontal scrolling for many steps.
• For docs/playgrounds: wrap horizontal steppers with `overflowX: auto` so they never break layout.
Live preview
Default vs scroll
"use client";
import { useMemo, useState } from "react";
import Stepper from "@/_components/stepper/Stepper";
export default function Example() {
const stepsMany = useMemo(
() => Array.from({ length: 12 }).map((_, i) => ({ id: String(i), label: "Step " + (i + 1) })),
[]
);
const stepsFew = useMemo(() => stepsMany.slice(0, 6), [stepsMany]);
const [a, setA] = useState(2);
const [b, setB] = useState(3);
return (
<>
<Stepper value={a} onChange={setA} steps={stepsFew} flow="default" />
<Stepper value={b} onChange={setB} steps={stepsMany} flow="scroll" />
</>
);
}Content mode
Panels are always rendered; `contentMode` controls their visibility via CSS classes.
Live preview
none / active / all
import Stepper from "@/_components/stepper/Stepper";
export default function Example() {
return (
<>
<Stepper value={0} contentMode="none" steps={[{ id: "a", label: "A", content: <div>Panel A</div> }]} />
<Stepper value={0} contentMode="active" steps={[{ id: "a", label: "A", content: <div>Panel A</div> }]} />
<Stepper value={0} contentMode="all" steps={[{ id: "a", label: "A", content: <div>Panel A</div> }]} />
</>
);
}Status + linear mode
Status can be set explicitly per step. `linear` blocks jumping too far ahead.
Live preview
linear, users can’t jump ahead more than one step.Statuses + linear
"use client";
import { useState } from "react";
import Stepper from "@/packages/ui/src/_components/stepper/Stepper";
export default function Example() {
const [active, setActive] = useState(0);
return (
<Stepper
value={active}
onChange={setActive}
linear
steps={[
{ id: "a", label: "Done", status: "complete" },
{ id: "b", label: "Error", status: "error" },
{ id: "c", label: "Disabled", status: "disabled" },
{ id: "d", label: "Default", status: "default" },
]}
/>
);
}On this page
Stepper Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| steps | StepperStep[] | Yes | — | Steps to render (label/supportText/badge/content + optional status). |
| value | number | Yes | — | Active step index (0-based). |
| onChange | (nextIndex: number, meta?: StepperChangeMeta) => void | No | — | Enables clickable behavior (unless `interaction='static'`). Called when a step is clicked and allowed. |
| orientation | "horizontal" | "vertical" | No | "horizontal" | Layout orientation. |
| size | "sm" | "md" | "lg" | No | "md" | Size modifier. |
| variant | "primary" | "secondary" | "tertiary" | "success" | "warning" | "danger" | "info" | No | "primary" | Visual tone / semantic variant. |
| flow | "default" | "scroll" | No | "default" | Flow behavior. `scroll` adds the scroll modifier class for long steppers. |
| interaction | "clickable" | "static" | No | onChange ? "clickable" : "static" | Forces whether steps can be clicked. |
| contentMode | "none" | "active" | "all" | No | "none" | Controls content visibility via CSS classes (panels are still rendered). |
| linear | boolean | No | false | When true, users can’t jump ahead more than one step. |
| aria-label | string | No | — | Passed to the root `<nav aria-label=...>` for accessibility. |
| className | string | No | — | Appended to the root classes. |
| customStyles | React.CSSProperties | No | — | Inline styles applied to the root `<nav>` element. |
Components