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.jsonUsage
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
- Fully controlled. There is no internal step state — the consumer owns
currentand decides how it changes.onChangeis invoked with the next zero-indexed step whenever a chevron is pressed; the component never advances on its own. - Edge clamping.
currentis clamped to the inclusive zero-indexed range before being announced. Whencurrentis at either edge, the matching chevron is disabled (no wrap-around) and its tap-scale is suppressed. - Counter is 1-indexed for humans. The visible counter renders
Step N / Mwith N as the 1-indexed step number. Override with thelabelstring for a custom static label,nullto hide the counter, orrenderLabelto render anything. - 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'sdanimator underSPRINGS.snap. - Right-side slot.
childrenrenders 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
| Prop | Type | Default | Description |
|---|---|---|---|
current | number | required | Current zero-indexed step. Clamped to the valid range. |
total | number | required | Total number of steps. Less than 1 disables navigation. |
onChange | (next: number) => void | required | Fired with the next clamped zero-indexed step. |
showPlayPause | boolean | false | Render the play / pause transport on the left. |
playing | boolean | false | Current transport state. Required when showPlayPause is true. |
onPlayPause | (next: boolean) => void | — | Fired with the next playing state on toggle. |
label | string | null | derived | Override the counter text. null hides the counter. |
renderLabel | (info) => ReactNode | — | Replace the counter entirely. Takes precedence over label. |
hideChevrons | boolean | false | Hide prev / next chevrons; counter and slot still render. |
children | ReactNode | — | Right-side slot rendered after the next chevron. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The root element carries
role="group"andaria-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 viaaria-pressedand its action viaaria-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
disabledon 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
transformandopacityonly; no layout-thrashing properties. The icon path morph is GPU-friendly via Motion'sdanimator.
Credits
- Extracted from:
algoflashcards(src/lessons/primitives/chrome/StepperControls.tsx— an alias re-export ofStepNavControls.tsx). The source coupled the bar to aStepThroughBaghook, aPredictionGatequiz state, and atrackHexaccent color. craft-bits' version drops the bag plumbing, surfaces a controlledcurrent+onChangeAPI, exposes the play / pause transport as an opt-in pair of props, and reduces the right-side gate / replay / custom-nav states to a singlechildrenslot.