Stepper
Progress through a known sequence of steps, announced rather than implied.
@luc/uiExamples
A completed step is a button
Steps at or before the current one are real buttons that report their index — go back to Account and the strip re-derives which steps are done. A step after the current one is static text, so nothing can jump past a prerequisite.
- ReviewConfirm and publish
const [current, setCurrent] = useState(2);
<Stepper steps={STEPS} current={current} onStepSelect={setCurrent} />A vertical checklist beside the work
`vertical` is the sidebar shape — a checklist beside the work it belongs to, not a header above it. Nothing else changes with the orientation: the marker, the connector and the reachability rule are identical, so a form header and a sidebar checklist cannot drift apart.
- AccountIdentity and roles
- DatasetWhat is in scope
- ScheduleWhen it runs
- ReviewConfirm and publish
<Stepper steps={STEPS} current={1} orientation="vertical" />Numbers instead of checks
`showNumbers` keeps the position legible once a step is done, which a check does not: the reader can see that this is step three of four.
- AccountIdentity and roles
- DatasetWhat is in scope
- ScheduleWhen it runs
- ReviewConfirm and publish
<Stepper steps={STEPS} current={2} showNumbers />A step finished out of order
`complete` on a step overrides the position: Review is done while Schedule is still the active step, which is what a step that can be finished early needs.
- AccountIdentity and roles
- DatasetSkipped: no new data
- ScheduleWhen it runs
- ReviewConfirm and publish
<Stepper
steps={[
{ id: "account", label: "Account", description: "Identity and roles" },
{ id: "dataset", label: "Dataset", description: "Skipped: no new data" },
{ id: "schedule", label: "Schedule", description: "When it runs" },
{ id: "review", label: "Review", description: "Confirm and publish", complete: true },
]}
current={2}
/>Long labels, and a narrow column
A label wraps inside its own step and the strip keeps its even `1fr` rhythm; when the row genuinely cannot fit, let the container scroll rather than the page.
- Universe and instrument scopeSectors, regions, liquidity floors
- Cost and slippage assumptionsPer venue, in basis points
- Risk limits and drawdown guardrailsHard and soft thresholds
<Stepper
steps={[
{ id: "scope", label: "Universe and instrument scope", description: "Sectors, regions, liquidity floors" },
{ id: "cost", label: "Cost and slippage assumptions", description: "Per venue, in basis points" },
{ id: "risk", label: "Risk limits and drawdown guardrails", description: "Hard and soft thresholds" },
]}
current={1}
/>Usage
import { Stepper } from "@luc/ui";<Stepper
steps={[
{ id: "account", label: "Account" },
{ id: "dataset", label: "Dataset" },
]}
current={1}
onStepSelect={setCurrent}
/>`current` is zero-based, and the steps after it are static text — that is the whole reachability rule.
Package setup — peer requirements, the stylesheet and the provider — is on installation.
API
Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.
One symbol. The table below is `Stepper`'s own props, including the `StepperStep` shape it takes and the `complete` override on a step.
| Export | Kind | Signature |
|---|---|---|
Stepper | 5 documented props | ({ className, steps, current, onStepSelect, orientation, showNumbers, ...props }: StepperProps) => import("@types/react").JSX.Element |
Stepper props
Type: StepperProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
current | number | — | Zero-based index of the active step. |
onStepSelect? | ((index: number) => void) | undefined | — | Jump to a step. Only steps before `current` call this. |
orientation? | "horizontal" | "vertical" | undefined | — | `horizontal` is a form header; `vertical` is a sidebar checklist. |
showNumbers? | boolean | undefined | — | Show the step number instead of a check when done. Default `false`. |
steps | readonly StepperStep[] | — |
Guidelines
Do
- Keep `current` as the single source of position and let completion derive from it; a step that can genuinely finish early says so with `complete`.
- Pass `onStepSelect` only where a step is revisitable — it is what turns the steps at or before the current one into buttons.
- Turn the strip on its side with `orientation="vertical"` when it belongs beside the work rather than above it.
Don't
- Do not use a stepper for a process with no fixed order — a checklist or a progress bar says that better.
- Do not let a step link forwards: everything after `current` is unreachable on purpose, because a form that can be submitted out of order is a validation problem.
- Do not put a step's whole meaning in its number: `aria-current` marks the position, and the label is what a screen reader reads.
- Do not let the strip push the page sideways: put it in a frame that scrolls instead — and give that frame a tab stop, because a keyboard user cannot drag a scrollbar.
Accessibility
The steps are an ordered list and `aria-current="step"` marks exactly one element: the step's button where it has one, otherwise its list item — so a reader tabbing through hears which step they are on rather than a run of buttons. The connectors between steps are decorative and hidden from assistive technology, a step past `current` is not focusable at all, and the marker shows a check or the step number while the label stays the accessible name.
Related