PixelStepper

Multi-step progress indicator with completed/active/pending/error/loading states, horizontal or vertical orientation, and full keyboard navigation.

  • Compound API (a stepper and its steps: PixelStepper.Step, PixelStepperStep in Vue, pxl-stepper-step in Angular) keeps step content declarative and easy to reorder.
  • Per-step states (completed, active, pending, error, loading) with tone-mapped indicators and connectors.
  • Horizontal or vertical orientation with roving focus, Arrow/Home/End keys, and Enter/Space activation.
  • Optional step clicks (onStepClick, @step-click in Vue, clickable + (stepClick) in Angular) with allowNextStepsSelect gate so future steps stay locked until allowed.
  • Surface-aware (pixel/linear) and size-aware (sm/md/lg), inheriting kit-wide tokens and focus rings.
Status
stable
Category
navigation
Since
v1.9.0

API

import { PixelStepper } from '@pxlkit/ui-kit';

PixelStepper

PropTypeDefaultDescription
active (required)number—Index of the current step, from 0.
onStepClick(idx: number) => void—Called with the index of a step that is clicked, or activated with Enter or Space. With it, the steps up to the active one — every step with allowNextStepsSelect — are clickable and in the tab order.
orientation'horizontal' | 'vertical''horizontal'Steps in a row or a column; also the arrow keys that move between them.
allowNextStepsSelectbooleanfalseWith onStepClick, makes the steps after the active one clickable too.
size'sm' | 'md' | 'lg''md'Size of the indicators and labels.
surface'pixel' | 'linear'—Surface override; defaults to the nearest provider.
ariaLabelstring'Progress steps'Accessible name for the steps landmark. Defaults to "Progress steps".
children (required)React.ReactNode—The steps (PixelStepper.Step).

Also takes the native attributes and event handlers of <div> (HTMLAttributes<HTMLDivElement>).

ref points to <div>.

PixelStepper.Step

PropTypeDefaultDescription
label (required)string—Label under (or beside) the indicator.
descriptionstring—Smaller text below the label.
iconReact.ReactNode—Custom icon in the indicator, shown while the step is neither completed nor in error.
loadingbooleanfalseShows a spinner in the indicator.
completedbooleanfalseMarks the step done, with a check mark.
errorbooleanfalseMarks the step failed, with a cross; wins over completed.

Also takes the native attributes and event handlers of <div> (HTMLAttributes<HTMLDivElement>), except children.

ref points to <div>.

Accessibility

WCAG target: 2.1 AA

  • progress-steps

The root renders role="group" with a configurable ariaLabel (defaults to "Progress steps"). A clickable step is role="button", named by its position ("Step N of M"), its label and its state (current/completed/error) and described by its description. A step that is not clickable (no step click handling, or a future step when allowNextStepsSelect is false) has no role and stays out of the tab sequence; since ARIA does not let an element without a role take a name, it reads its position and state as visually hidden text around its label. The active step is marked with aria-current="step". Indicators and connectors are aria-hidden so screen readers announce only the step label and state.

Keyboard

KeyActionWhen
TabMoves focus into/out of the stepper; only clickable steps participate in the tab sequence.—
ArrowRightFocuses the next clickable step.orientation="horizontal"
ArrowLeftFocuses the previous clickable step.orientation="horizontal"
ArrowDownFocuses the next clickable step.orientation="vertical"
ArrowUpFocuses the previous clickable step.orientation="vertical"
HomeFocuses the first clickable step.—
EndFocuses the last clickable step.—
EnterActivates the focused step (when clickable).—
SpaceActivates the focused step (when clickable).—

Usage

import { PixelStepper } from '@pxlkit/ui-kit';

export function Default() {
  return (
    <PixelStepper active={1}>
      <PixelStepper.Step label="Account" description="Create your account" />
      <PixelStepper.Step label="Profile" description="Add some details" />
      <PixelStepper.Step label="Confirm" description="Review and submit" />
    </PixelStepper>
  );
}

Examples

Default

import { PixelStepper } from '@pxlkit/ui-kit';

export function Default() {
  return (
    <PixelStepper active={1}>
      <PixelStepper.Step label="Account" description="Create your account" />
      <PixelStepper.Step label="Profile" description="Add some details" />
      <PixelStepper.Step label="Confirm" description="Review and submit" />
    </PixelStepper>
  );
}

Interactive

import { useState } from 'react';
import { PixelStepper } from '@pxlkit/ui-kit';

export function Interactive() {
  const [active, setActive] = useState(0);
  const total = 4;
  return (
    <div className="space-y-3">
      <PixelStepper active={active} onStepClick={setActive}>
        <PixelStepper.Step label="Plan" description="Pick a tier" />
        <PixelStepper.Step label="Billing" description="Payment method" />
        <PixelStepper.Step label="Confirm" description="Review charges" />
        <PixelStepper.Step label="Done" description="All set" />
      </PixelStepper>
      <div className="flex gap-2">
        <button
          type="button"
          className="text-xs px-2 py-1 border border-retro-border"
          onClick={() => setActive((i) => Math.max(0, i - 1))}
        >
          Back
        </button>
        <button
          type="button"
          className="text-xs px-2 py-1 border border-retro-border"
          onClick={() => setActive((i) => Math.min(total - 1, i + 1))}
        >
          Next
        </button>
      </div>
    </div>
  );
}

Vertical

import { PixelStepper } from '@pxlkit/ui-kit';

export function Vertical() {
  return (
    <PixelStepper active={1} orientation="vertical">
      <PixelStepper.Step label="Upload" description="Pick a file" />
      <PixelStepper.Step label="Process" description="Running checks" loading />
      <PixelStepper.Step label="Publish" description="Make it live" />
    </PixelStepper>
  );
}

States

import { PixelStepper } from '@pxlkit/ui-kit';

export function States() {
  return (
    <PixelStepper active={2}>
      <PixelStepper.Step label="Created" completed />
      <PixelStepper.Step label="Validated" completed />
      <PixelStepper.Step label="Signing" loading />
      <PixelStepper.Step label="Failed" error />
      <PixelStepper.Step label="Done" />
    </PixelStepper>
  );
}

Sizes

import { PixelStepper } from '@pxlkit/ui-kit';

export function Sizes() {
  return (
    <div className="space-y-6">
      <PixelStepper active={1} size="sm">
        <PixelStepper.Step label="One" />
        <PixelStepper.Step label="Two" />
        <PixelStepper.Step label="Three" />
      </PixelStepper>
      <PixelStepper active={1} size="md">
        <PixelStepper.Step label="One" />
        <PixelStepper.Step label="Two" />
        <PixelStepper.Step label="Three" />
      </PixelStepper>
      <PixelStepper active={1} size="lg">
        <PixelStepper.Step label="One" />
        <PixelStepper.Step label="Two" />
        <PixelStepper.Step label="Three" />
      </PixelStepper>
    </div>
  );
}

Surfaces

import { PixelStepper } from '@pxlkit/ui-kit';

export function Surfaces() {
  return (
    <div className="grid grid-cols-1 gap-6">
      <PixelStepper active={1} surface="pixel" ariaLabel="Pixel stepper">
        <PixelStepper.Step label="Start" />
        <PixelStepper.Step label="Build" />
        <PixelStepper.Step label="Ship" />
      </PixelStepper>
      <PixelStepper active={1} surface="linear" ariaLabel="Linear stepper">
        <PixelStepper.Step label="Start" />
        <PixelStepper.Step label="Build" />
        <PixelStepper.Step label="Ship" />
      </PixelStepper>
    </div>
  );
}

Allow next steps

import { useState } from 'react';
import { PixelStepper } from '@pxlkit/ui-kit';

export function AllowNextStepsSelect() {
  const [active, setActive] = useState(1);
  return (
    <PixelStepper active={active} onStepClick={setActive} allowNextStepsSelect>
      <PixelStepper.Step label="Intro" description="Welcome" />
      <PixelStepper.Step label="Details" description="Tell us more" />
      <PixelStepper.Step label="Review" description="Almost there" />
      <PixelStepper.Step label="Finish" description="Complete" />
    </PixelStepper>
  );
}

Related