Progress stepper
Step indicator for multi-step flows. Two variants — labeled vertical steps and compact horizontal dots.
ProgressStepper shows where the user is in a multi-step flow. One component, two layouts: steps (labeled, vertical or horizontal with descriptions) and dots (compact horizontal indicator). Both share the same activeStep, linear, and onStepClick model.
The dots variant replaced the standalone ProgressDots component per ADR-004 §3 — same shape, same logic, different layout = one component with a variant prop, not two.
Note: this is not the same as Stepper — that's a numeric +/− input.
Use it for
- Checkout / signup / setup wizard step indicators
- Multi-page form progress
- Onboarding flow markers
- Any sequential flow where the user benefits from seeing position + total steps
For boolean done/in-progress on a single task, use ProgressBar. For numeric input, use Stepper.
Import
import { ProgressStepper } from '@brikdesigns/bds';Variants
Steps
The default labeled variant.
<ProgressStepper
steps={[
{ label: 'Account' },
{ label: 'Profile' },
{ label: 'Review' },
]}
activeStep={1}
/>Steps with descriptions
<ProgressStepper
steps={[
{ label: 'Details', description: 'Enter your information' },
{ label: 'Payment', description: 'Add billing details' },
{ label: 'Confirm', description: 'Review and submit' },
]}
activeStep={0}
onStepClick={(step) => setStep(step)}
/>Dots variant
The active dot stretches wider; completed dots are dimmed; upcoming dots are muted. No label text — pair with a page heading or step counter.
<ProgressStepper
variant="dots"
count={5}
activeStep={2}
onStepClick={(step) => setStep(step)}
/>Linear mode
Restrict navigation to sequential steps — clicking a future step beyond activeStep + 1 is blocked. Use for flows where steps depend on completion of prior ones.
<ProgressStepper
steps={steps}
activeStep={1}
linear
onStepClick={(step) => setStep(step)}
/>Sizes
sm for constrained layouts, md (default) otherwise.
<ProgressStepper steps={steps} activeStep={0} size="sm" />When not to use
Don't use ProgressStepper for measurable progress within a single task. That's ProgressBar. ProgressStepper is for between discrete steps; ProgressBar is for within a single step.
- Don't use Steps with >5 entries. A 7-step labeled flow is unscannable — switch to
dotsand lean on the page heading for context. - Don't use Dots for unfamiliar flows. Without labels, dots assume the user already knows what each step is about. Use Steps for first-time-user flows.
Accessibility
- The steps variant renders a
<nav role="list" aria-label="Progress">; the dots variant renders a<nav role="tablist">. - Active step carries
aria-current="step"(steps variant); the dots variant marks itaria-selected. - With
onStepClick, each step becomes keyboard-operable — the steps variant uses arole="button"element withtabindex+Enter/Spacehandling and a focus ring; the dots variant uses a real<button>. linearmode disables future steps viaaria-disabledso assistive tech announces the constraint.
API
ProgressStepperProps is a discriminated union of two shapes, so variant selects
which of steps / count is required. The shared props first:
| Prop | Type | Default |
|---|---|---|
activeStep | number (required) | — |
size | ProgressStepperSize | 'md' |
linear | boolean | false |
onStepClick | (index: number) => void | — |
Then one of the two variant shapes:
| Prop | Type | Default |
|---|---|---|
variant | 'steps' | 'steps' |
steps | ProgressStep[] (required) | — |
| Prop | Type | Default |
|---|---|---|
variant | 'dots' (required) | — |
count | number (required) | — |
activeStep is zero-indexed. steps and count are mutually exclusive — the union
makes passing the wrong one a type error rather than a runtime surprise.
ProgressStep
interface ProgressStep {
label: string;
description?: string;
}Related
- ProgressBar — within-step progress
- ProgressCircle — single-value circular progress
- Stepper — different component (numeric input)
- Pagination — page navigation, not flow progress
- Storybook playground