Steps
A step progress tracker with optional navigation buttons, validation, and controlled or uncontrolled state.
Steps is a client component that renders a StepProgress track of numbered indicators and, optionally, previous/next buttons with a step counter. Use it for multi-step flows such as checkout or onboarding. Indicators animate with motion/react and show labels in Reactberry's Tooltip; no provider is required.
Import#
import { Steps, StepProgress, StepIndicator, StepsNav, useStepNavigation } from "@reactberry/system/blocks";
import type { StepConfig, StepThemeConfig, StepVariant, BaseStepProps } from "@reactberry/system/blocks";
Usage#
import { Steps, type StepConfig } from "@reactberry/system/blocks";
import { Box } from "@reactberry/system/elements";
const steps: StepConfig[] = [
{ label: "Account" },
{ label: "Profile" },
{ label: "Billing" },
{ label: "Review" },
];
export default function Onboarding() {
return (
<Box maxWidth="40rem" mx="auto" p="l">
<Steps
config={steps}
showNavigation
validateStep={(from, to) => to <= from + 1}
onStepChange={(step) => console.log("Step", step)}
onComplete={() => console.log("Done")}
/>
</Box>
);
}
Examples#
Controlled#
Set controlled and pass activeStep to drive the step from your own state. onStepChange receives the target step when an indicator is clicked.
Validation and completion#
validateStep cancels navigation when it returns false; here it blocks skipping ahead. navigationLabels replaces the button text, and onComplete runs when the finish button is pressed on the last step.
Compact track#
simple hides numbers and check icons; combine it with showLabels={false}, showTooltip={false} and showEdges={false} for a minimal progress bar. allowStepClick={false} makes the indicators read-only.
API#
Steps#
| Prop | Type | Default | Description |
|---|---|---|---|
initialStep | number | 0 | Initial active step (0-based) |
controlled | boolean | false | Whether navigation is controlled externally |
activeStep | number | — | Current active step when controlled |
onStepChange | ((step: number) => void) | — | Callback when step changes |
showNavigation | boolean | false | Whether to show navigation buttons |
navigationLabels | { next?: string; previous?: string; finish?: string | undefined; } | undefined | {… | Custom labels for navigation buttons |
allowStepClick | boolean | true | Whether steps can be clicked to navigate |
validateStep | ((fromStep: number, toStep: number) => boolean | Promise<boolean>) | — | Validation function for step navigation |
loading | boolean | false | Loading state |
onComplete | (() => void) | — | Callback when all steps are completed |
StepsProps extends Omit<BaseStepProps, "active">, so it also takes config (StepConfig[], required) and variant (default "light"), skin, simple (default false), showLabels, showEdges, showTooltip (default true) and childProps, which are passed to StepProgress (see below). activeStep falls back to 0 when undefined. Passing navigationLabels replaces all three default labels. Remaining props are spread onto StepProgress, which spreads them onto its container Box.
StepProgress#
Renders a CSS grid with one StepIndicator per step, an optional pair of edge circles, and a progress track filled up to the active step. Steps before active render as completed.
| Prop | Type | Default | Description |
|---|---|---|---|
completed | number | — | Index of the completed step (all steps up to this index are completed) |
config* | StepConfig[] | — | Array of step configurations |
active* | number | — | Index of the currently active step (0-based) |
skin | StepThemeConfig | — | Custom theme configuration |
variant | "light""dark" | "light" | Theme variant to use |
showLabels | boolean | true | Whether to show step labels |
showEdges | boolean | true | Whether to show edge circles |
showTooltip | boolean | true | Whether to show tooltips on hover |
simple | boolean | false | Whether to use simple mode (no numbers/icons) |
childProps | Record<string, any> | — | Additional props to pass to child components |
setActive | ((index: number) => void) | — | Callback when a step is clicked |
skin replaces the built-in theme for variant entirely. childProps is spread onto every StepIndicator after the step's own config. completed is declared but not used; it is spread onto the container Box with the remaining props.
StepsNav#
A thin wrapper around StepProgress for externally managed state. All props other than mt and showLabels are passed to StepProgress.
| Prop | Type | Default | Description |
|---|---|---|---|
mt | string | number | object | medium | Additional margin top spacing |
config* | StepConfig[] | — | Array of step configurations |
active* | number | — | Index of the currently active step (0-based) |
skin | StepThemeConfig | — | Custom theme configuration |
variant | "light""dark" | — | Theme variant to use |
showLabels | boolean | true | Whether to show step labels |
showEdges | boolean | — | Whether to show edge circles |
showTooltip | boolean | — | Whether to show tooltips on hover |
simple | boolean | — | Whether to use simple mode (no numbers/icons) |
childProps | Record<string, any> | — | Additional props to pass to child components |
setActive | ((index: number) => void) | — | Callback when a step is clicked |
StepIndicator#
Renders one step: a circular dot and an optional label, wrapped in Tooltip (showing label) when showTooltip is true.
| Prop | Type | Default | Description |
|---|---|---|---|
active* | boolean | — | Whether this step is currently active |
index* | number | — | The step number (1-based) |
label* | string | — | The label text for this step |
completed* | boolean | — | Whether this step is completed |
variant* | StepThemeConfig | — | Theme configuration |
simple | boolean | false | Whether to use simple mode (no numbers/icons) |
showLabels | boolean | true | Whether to show labels |
showTooltip | boolean | true | Whether to show tooltips on hover |
setActive | ((index: number) => void) | — | Callback when step is clicked |
variant is the resolved theme object, not a StepVariant string. active scales the dot up and shows the step number; completed shows a check icon. setActive is called with index - 1 on click and switches the cursor to pointer. Remaining props are spread onto the indicator Box.
useStepNavigation#
Parameters
| Prop | Type | Default | Description |
|---|---|---|---|
totalSteps* | number | — | Number of steps. |
initialStep | number | 0 | Initial 0-based step. |
Returns
| Prop | Type | Default | Description |
|---|---|---|---|
activeStep | number | — | Current 0-based step. |
nextStep | () => void | — | Moves forward one step, clamped to |
prevStep | () => void | — | Moves back one step, clamped to |
goToStep | (step: number) => void | — | Sets the step if it is within range; otherwise does nothing. |
isFirst | boolean | — |
|
isLast | boolean | — |
|
progress | number | — |
|
setActiveStep | Dispatch<SetStateAction<number>> | — | The raw state setter, without clamping. |
StepConfig#
| Prop | Type | Default | Description |
|---|---|---|---|
label* | string | — | Label and tooltip text. |
path | string | — | Declared; not used. |
canNavigate | boolean | — | Declared; not used. |
[key: string] | any | — | Other keys are spread onto the |
StepThemeConfig#
All fields are required. StepVariant is "light" | "dark".
| Prop | Type | Default | Description |
|---|---|---|---|
baseColor* | string | — | Label colour of the active step. |
textColor* | string | — | Label colour of other steps. |
borderColor* | string | — | Border colour of the dots and edge circles. |
trackColor* | string | — | Track background colour. |
completedTrackColor* | string | — | Colour of the filled part of the track. |
size* | string | — | Track height; dots are |
unit* | string | — | CSS unit for |
fontSize* | string | — | Font size of numbers and labels. |
presets* | { active, completed, default: { bg: string; color: string } } | — | Dot colours per state. |
Built-in themes: light uses an accent completed track and accent active/completed dots; dark uses a brand completed track, white active dots, and primary completed dots. Both use a 0.3rem track and fontSize "small".
Accessibility#
- Navigation buttons are native
buttonelements withtype="button", so they are keyboard accessible and do not submit an enclosingform.loadingsetsdisabledon both. - Step indicators are
divelements with a click handler and no role,tabIndexor keyboard handling, so they can't be reached or activated with the keyboard. - The step counter is plain text ("N of M") with no live region.
Notes#
- Each
StepConfigobject is spread onto itsStepIndicator, so extra keys (includingpathandcanNavigate) reach the indicatorBoxand the DOM.pathandcanNavigateare declared but not used;canNavigate: falsedoes not block navigation. - In controlled mode,
isFirstandisLaststill come from the internal step state (seeded byinitialStep), so the previous button visibility, the finish label, andonCompletedo not followactiveStep.validateStepis not called for the next button in controlled mode. - A
setActiveprop passed toStepsis spread after the internal click handler and replaces it. - Without
onComplete, pressing next on the last step keeps the step clamped but still callsonStepChangewithconfig.length. - Labels are hidden on the smallest breakpoint (
displayof["none", "flex"]).