Stepper
- Needs review
- WCAG 2.2 evidence
- RTL
Shows where someone is in a flow whose steps have names, such as onboarding or a leave request. It renders
an ordered list named by aria-label of the already built Step (list semantics, one position each; WCAG 1.3.1). The current
step carries aria-current="step"; every status is written as text ("Done", "In progress", "Up next", "Needs attention") and
drawn with a different shape, never by colour alone (1.4.1, 4.1.2). Steps with href or onPress become links or buttons
in the Tab order (Enter, and Space for buttons; 2.1.1) whose whole step is the target (2.5.8) and which show the focus ring
around that whole step (2.4.7); the others stay plain text. Horizontal steppers become vertical in containers narrower than 480 px, so long titles keep wrapping instead of being cut at
320 px (1.4.10); the connector lines follow the reading direction and mirror in right-to-left layouts. The Stepper has no state
or effects and renders in React Server Components; only a navigable step loads a small client island. It does not move
focus or announce changes: when the current step changes, move focus to the new step's heading (or announce it with
announceStatus) (4.1.3). Use ProgressSteps when the steps need no names, and a checklist when they can be done in any order. Consumer duties: step
titles, translated status texts, the list name, and deciding which steps are navigable. Figma proposes arrow-key movement for
steppers (spec section 7); here every navigable step is a Tab stop instead, which keeps a plain Tab order and needs no extra
keyboard help.
import { Stepper } from "@nexera-ui/react";- 8
- examples
- 6
- props
- 1
- live controls
- 1
- platform
- 11
- WCAG criteria
- 4
- blocks use it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { Stepper } from "@nexera-ui/react";
<Stepper />Examples 7
The same examples as Storybook, rendered live. Open Code to copy one.
Vertical
Figma Orientation=Vertical (320 px wide): a rail runs beside the titles; use it in side panels.
import { Stepper } from "@nexera-ui/react";
const onboarding: readonly StepperStep[] = [
{ label: "Account" },
{ label: "Personal details" },
{ label: "Documents" },
{ label: "Review" },
];
export function Vertical() {
return (
<div className="max-w-xs">
<Stepper
aria-label="Onboarding progress"
steps={onboarding}
current={3}
orientation="horizontal"
/>
</div>
);
}
With error
A step can show an error: set its status, and replace the status text with what needs fixing (the status is still read first).
import { Stepper } from "@nexera-ui/react";
const onboarding: readonly StepperStep[] = [
{ label: "Account" },
{ label: "Personal details" },
{ label: "Documents" },
{ label: "Review" },
];
export function WithError() {
return (
<div className="max-w-[40rem]">
<Stepper
aria-label="Onboarding progress"
steps={onboarding}
current={3}
orientation="horizontal"
/>
</div>
);
}
Navigable steps
href turns a step into a link and onPress into a button; steps with neither stay plain text. Tab through them.
import { Stepper } from "@nexera-ui/react";
const onboarding: readonly StepperStep[] = [
{ label: "Account" },
{ label: "Personal details" },
{ label: "Documents" },
{ label: "Review" },
];
export function NavigableSteps() {
return (
<div className="max-w-[40rem]">
<Stepper
aria-label="Onboarding progress"
steps={onboarding}
current={3}
orientation="horizontal"
/>
</div>
);
}
Wizard
A controlled wizard: finished steps jump back with onPress, the buttons move forward and announce the change.
import { useState } from "react";
import { Button, Stepper } from "@nexera-ui/react";
const onboarding: readonly StepperStep[] = [
{ label: "Account" },
{ label: "Personal details" },
{ label: "Documents" },
{ label: "Review" },
];
function WizardExample({
orientation = "horizontal",
}: {
orientation?: "horizontal" | "vertical";
}) {
const [current, setCurrent] = useState(2);
const names = ["Account", "Personal details", "Documents", "Review"];
const steps = names.map((label, index) => ({
label,
...(index + 1 < current
? {
onPress: () => {
setCurrent(index + 1);
},
}
: {}),
}));
return (
<div className="flex max-w-[40rem] flex-col gap-6">
<Stepper
aria-label="Onboarding progress"
current={current}
orientation={orientation}
steps={steps}
/>
<p className="m-0 text-body-default text-secondary">
Step {current} of {names.length}: {names[current - 1]}
</p>
<div className="flex gap-2">
<Button
variant="secondary"
isDisabled={current === 1}
onPress={() => {
setCurrent((value) => Math.max(1, value - 1));
}}
>
Back
</Button>
<Button
isDisabled={current > names.length}
onPress={() => {
setCurrent((value) => Math.min(names.length + 1, value + 1));
}}
>
{current === names.length ? "Finish" : "Next"}
</Button>
</div>
</div>
);
}
export function Wizard() {
return (
<WizardExample
aria-label="Onboarding progress"
steps={onboarding}
current={3}
orientation="horizontal"
/>
);
}
Long labels
Long titles wrap instead of truncating, so the stepper still fits at 320 px.
import { Stepper } from "@nexera-ui/react";
const onboarding: readonly StepperStep[] = [
{ label: "Account" },
{ label: "Personal details" },
{ label: "Documents" },
{ label: "Review" },
];
export function LongLabels() {
return (
<div className="max-w-[40rem]">
<Stepper
aria-label="Onboarding progress"
steps={onboarding}
current={3}
orientation="horizontal"
/>
</div>
);
}
Narrow container
The container query: the same horizontal stepper in containers of 640, 480 and 320 px. Below 480 px of container width (not viewport) it stacks into the vertical layout.
import { Stepper } from "@nexera-ui/react";
const onboarding: readonly StepperStep[] = [
{ label: "Account" },
{ label: "Personal details" },
{ label: "Documents" },
{ label: "Review" },
];
export function NarrowContainer() {
return (
<div className="flex flex-col gap-8">
{["max-w-[40rem]", "max-w-[30rem]", "max-w-[20rem]"].map((width) => (
<div key={width} className={`${width} rounded-md border border-default p-4`}>
<Stepper
aria-label="Onboarding progress"
steps={onboarding}
current={3}
orientation="horizontal"
/>
</div>
))}
</div>
);
}
Right to left
Right-to-left: the steps run from right to left and the connector lines follow.
import { Stepper } from "@nexera-ui/react";
const onboarding: readonly StepperStep[] = [
{ label: "Account" },
{ label: "Personal details" },
{ label: "Documents" },
{ label: "Review" },
];
export function RightToLeft() {
return (
<div className="max-w-[40rem]">
<Stepper
aria-label="Onboarding progress"
steps={onboarding}
current={3}
orientation="horizontal"
/>
</div>
);
}
Props 6
Press "Try it" on a card to load that prop into the playground.
6 props shown
steps*Nexerareadonly StepperStep[]The steps, in order. Every entry takes the props of Step (label is required) plus href or onPress to make it
navigable. The Stepper uses an array instead of Step children because the orientation is a prop of each Step (a React
Server Component cannot pass it down through context), and because navigable steps need a link or button that the Stepper
builds.
currentNexeranumberThe step the person is on, counted from 1. It decides the status of every step that has no status of its own: before it is
complete, it is current (aria-current="step"), after it is upcoming. A value past the last step marks every step
complete; 0 leaves every step upcoming.
orientationNexera"horizontal" | "vertical"Layout: horizontal puts the steps in a row, vertical stacks them. A horizontal stepper
switches to the vertical layout by itself when its container is narrower than 480 px (container query, not the viewport);
vertical stays vertical.
classNameNexerastringExtra classes for the root, merged last so they win over the defaults.
aria-labelNexerastringAccessible name of the ordered list, for example "Onboarding progress". Name each stepper when a page has several. Translate it.
aria-labelledbyNexerastringId of a visible element that names the list; wins over aria-label.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
6 direct · 5 supporting- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.3.2Meaningful SequenceLevel A · supporting test
- 1.4.1Use of ColorLevel A · tested directly
- 1.4.10ReflowLevel AA · supporting test
- 1.4.12Text SpacingLevel AA · supporting test
- 2.1.1KeyboardLevel A · tested directly
- 2.4.3Focus OrderLevel A · tested directly
- 2.4.4Link Purpose (In Context)Level A · tested directly
- 2.4.7Focus VisibleLevel AA · supporting test
- 2.5.8Target Size (Minimum)Level AA · supporting test
- 4.1.2Name, Role, ValueLevel A · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
data-focus-visibledata-hovereddata-pressed
<Stepper className="data-focus-visible:opacity-90 shadow-sm" />Used in blocks
- Onboarding stepsOnboarding
- Multi-step formForms and data entry
- Project progressWork and projects
- Short surveyFeedback and surveys
Related components
- BreadcrumbShows where the current page sits in the hierarchy and links back to each level.
- BreadcrumbItemOne level of a `Breadcrumb` trail; use it only inside `Breadcrumb`.
- CursorPagerA previous and next pair for data without stable page numbers, such as an activity log or a cursor API.
- FABThe main action of a mobile screen: a floating button with a `nav/active-bg` fill and a large shadow.
- FeedStatusThe status line at the bottom of an infinite feed or a lazy list: "Loading more", "You have reached the end" or "Could not load more" with a retry action.
- JumpToPageA small "Go to page [48] of 120" control for long paged lists.
- NavigationBarThe bottom bar of a mobile layout: a named `nav` landmark with a list of three to five `NavItem`s.
- NavItemOne destination or action of a `NavigationBar`.