Headless / Navigation

Stepper

Stepper shows progress through an ordered sequence — a checkout, a wizard, a multi-stage form. Each step has a status; the active one is marked for assistive technology.

Loading playground…
Updated 3 days agoEdit on GitHubWeb & native

What it is

A single component over an array of StepperStep objects. It renders an ordered list and hands each step to your renderStep function, along with the resolved status and an isLast flag. Status comes from one of two places: the current prop names the active step, or each step carries its own status. Stepper is structure and status; the visuals are yours.

Install

example.tsx
yarn add @usemotif/headless
example.tsx
import { Stepper } from '@usemotif/headless';

API

Stepper

stable
function Stepper(props: StepperProps): JSX.Element
stepsreadonly StepperStep[]required

The ordered steps. Each is `{ id, label, status? }`.

currentstring

The id of the active step. Overrides any per-step `status: "active"`.

renderStep(info) => ReactElementrequired

Renders one step. `info` carries `step`, `index`, the resolved `status`, and `isLast`.

orientation"horizontal" | "vertical"= "horizontal"

Lays the steps out in a row or a column.

styleCSSProperties

Inline style for the list.

StepperStep

example.tsx
interface StepperStep {
id: string;
label: ReactNode;
status?: 'pending' | 'active' | 'complete' | 'error';
}

Accessibility

Stepper renders an <ol> — an ordered list, because the steps have an order. The active step's list item carries aria-current="step", so a screen-reader user knows which stage they are on. Status beyond "active" is visual; if complete or error carries meaning a non-sighted user needs, put it in the step's label text, not the colour alone.

A Stepper shows progress; it does not navigate. If steps are clickable — jump back to step 1 — make each a real button or link inside renderStep, and decide deliberately which completed steps are revisitable.

Examples

A checkout flow:

example.tsx
const steps = [
{ id: 'cart', label: 'Cart' },
{ id: 'shipping', label: 'Shipping' },
{ id: 'payment', label: 'Payment' },
{ id: 'review', label: 'Review' },
];
 
<Stepper
steps={steps}
current={currentStep}
renderStep={({ step, index, status, isLast }) => (
  <HStack gap="$2" alignItems="center">
    <Center
      w={24}
      h={24}
      borderRadius="$radii.full"
      bg={status === 'pending' ? '$colors.surface.muted' : '$colors.action.primary.bg'}
    >
      {status === 'complete' ? '✓' : index + 1}
    </Center>
    <Text fontWeight={status === 'active' ? '$semibold' : undefined}>{step.label}</Text>
  </HStack>
)}
/>