Stepper Controls

The transport row that sits under any step-through interaction — algorithm walkthrough, animation player, multi-phase explainer. Prev / next chevrons flank a Step N / M counter, with an optional play / pause control on the left and an open slot on the right for replay buttons or gate pills. Fully controlled: the consumer owns current and reacts to onChange.

Scan the array left to right.

Step 1 / 4

Installation

npx shadcn@latest add https://craftbits.dev/r/stepper-controls.json

Usage

import { StepperControls } from "@craft-bits/core";
 
<StepperControls
  current={index}
  total={steps.length}
  onChange={setIndex}
/>

Wire the optional play / pause transport:

<StepperControls
  current={index}
  total={steps.length}
  onChange={setIndex}
  showPlayPause
  playing={playing}
  onPlayPause={setPlaying}
/>

Hide the chevrons and render a bespoke control set via children, keeping the counter intact:

<StepperControls
  current={index}
  total={steps.length}
  onChange={setIndex}
  hideChevrons
>
  <ReplayButton onClick={() => setIndex(0)} />
</StepperControls>

Understanding the component

  1. Fully controlled. There is no internal step state — the consumer owns current and decides how it changes. onChange is invoked with the next zero-indexed step whenever a chevron is pressed; the component never advances on its own.
  2. Edge clamping. current is clamped to the inclusive zero-indexed range before being announced. When current is at either edge, the matching chevron is disabled (no wrap-around) and its tap-scale is suppressed.
  3. Counter is 1-indexed for humans. The visible counter renders Step N / M with N as the 1-indexed step number. Override with the label string for a custom static label, null to hide the counter, or renderLabel to render anything.
  4. Play / pause as a sibling, not a wrapper. The transport is opt-in. When enabled it sits to the left of the prev chevron and toggles via onPlayPause. The icon morphs between a triangle and bars via Motion's d animator under SPRINGS.snap.
  5. Right-side slot. children renders at the end of the row, after the next chevron. Use it for replay buttons, completion pills, or a prediction-gate node — the slot does not affect the centering of the counter.

Props

PropTypeDefaultDescription
currentnumberrequiredCurrent zero-indexed step. Clamped to the valid range.
totalnumberrequiredTotal number of steps. Less than 1 disables navigation.
onChange(next: number) => voidrequiredFired with the next clamped zero-indexed step.
showPlayPausebooleanfalseRender the play / pause transport on the left.
playingbooleanfalseCurrent transport state. Required when showPlayPause is true.
onPlayPause(next: boolean) => voidFired with the next playing state on toggle.
labelstring | nullderivedOverride the counter text. null hides the counter.
renderLabel(info) => ReactNodeReplace the counter entirely. Takes precedence over label.
hideChevronsbooleanfalseHide prev / next chevrons; counter and slot still render.
childrenReactNodeRight-side slot rendered after the next chevron.
classNamestringMerged onto the root via cn().

Accessibility

  • The root element carries role="group" and aria-label="Stepper controls" so assistive tech announces the bar as a single navigation unit.
  • The prev / next buttons have explicit aria-labels (Previous step, Next step, Last step). The play / pause button announces both its current state via aria-pressed and its action via aria-label.
  • The counter region carries aria-live="polite" so screen readers re-announce the new step number when navigation changes the value, without interrupting the user.
  • Disabled chevrons set disabled on the underlying button — they are skipped by keyboard focus and refuse pointer events.
  • Focus styles use the cb-accent ring with an offset against the cb-bg surface, meeting the 3:1 non-text contrast requirement at every variant.
  • Animation is transform and opacity only; no layout-thrashing properties. The icon path morph is GPU-friendly via Motion's d animator.

Credits

  • Extracted from: algoflashcards (src/lessons/primitives/chrome/StepperControls.tsx — an alias re-export of StepNavControls.tsx). The source coupled the bar to a StepThroughBag hook, a PredictionGate quiz state, and a trackHex accent color. craft-bits' version drops the bag plumbing, surfaces a controlled current + onChange API, exposes the play / pause transport as an opt-in pair of props, and reduces the right-side gate / replay / custom-nav states to a single children slot.