Step Progress

A thin horizontal row of indicators representing steps in a flow. The current step is visually emphasised (larger / wider / filled); past steps fade to a muted accent tint; future steps stay neutral. Three geometries — dots, pills, numbered — pick the one that matches the density of your context.

Different from StepTimeline: this primitive carries no labels and no connectors. Use it for wizards, multi-step forms, slideshow paginators, and any other context where step count + cursor are the only signals worth conveying.

Customize
Total
5
Current
2
Variant
dots
Size
md

Installation

npx shadcn@latest add https://craftbits.dev/r/step-progress.json

Usage

import { StepProgress } from "@craft-bits/core";
 
<StepProgress aria-label="Sign-up progress" total={5} current={2} />

Pills (the current-step capsule widens — a "now playing" cursor):

<StepProgress total={4} current={0} variant="pills" />

Numbered, for explicit step counts (≤ 9 ideally — beyond that, prefer StepTimeline):

<StepProgress total={4} current={1} variant="numbered" />

Understanding the component

  1. Three indicator geometries. dots (small filled circles), pills (short capsules that widen on the current step), numbered (circles hosting the 1-based step label). All three share the same [done | current | upcoming] palette logic — only the geometry changes.
  2. Snap on the cursor. The current-step emphasis (scale-up for dots/numbered, width grow for pills) rides on SPRINGS.snap so the cursor lands crisply rather than floats.
  3. Reduced motion. When prefers-reduced-motion: reduce is set, the cursor jumps to its new position with no spring — indicators repaint instantly.
  4. Clamped indices. total is floored to ≥ 1; current is clamped into [0, total - 1]. Out-of-range inputs render the closest valid cursor — the component never throws.
  5. Semantic markup. Renders as a <div role="progressbar"> with aria-valuemin={1}, aria-valuemax={total}, aria-valuenow={current + 1}, and an aria-valuetext of "Step X of Y". Indicators are aria-hidden.

Props

PropTypeDefaultDescription
totalnumberrequiredTotal number of steps. Floored to ≥ 1.
currentnumberrequired0-indexed current step. Clamped into [0, total - 1].
variant'dots' | 'pills' | 'numbered''dots'Indicator geometry.
size'sm' | 'md' | 'lg''md'Indicator dimensions.
classNamestringMerged onto the root <div> via cn().

Accessibility

  • Renders with role="progressbar" plus aria-valuemin={1}, aria-valuemax={total}, aria-valuenow={current + 1}, and aria-valuetext={"Step X of Y"} so assistive tech announces both the numeric and natural-language reading.
  • Indicator elements are marked aria-hidden="true" — screen readers read the bar's value, not the dot count.
  • The component is not auto-labelled. Provide aria-label or aria-labelledby on the root for context (e.g., "Sign-up progress").
  • prefers-reduced-motion: reduce short-circuits the cursor spring — indicators snap to their new state with no animation.

Credits

  • Extracted from: algoflashcards (src/lessons/primitives/chrome/StepProgress.tsx). Generalized: the source rendered a thin progress bar plus an "N / M" label via the project's StepChrome compound; the library version trades that for the canonical "row of indicators" idiom used by wizards and paginators, splits it into three geometric variants, and replaces the project's trackInfo.hex color prop with the --cb-accent token. Use the sibling StepTimeline when you want labels + connectors.