Step Chrome
The chrome shell for one step of a multi-step lesson, walkthrough, or onboarding flow. Stacks three slots: a header row with the step title and a counter, a thin progress meter beneath, the step body (your visualization, prose, or interactive widget), and an optional footer for nav controls or a hint line.
Reach for it when a stepped flow needs consistent surrounding chrome but every step has bespoke body content. Sibling to StepCaption (which morphs a single narration line) and StageHeader (the bare kicker without a body slot or footer); StepChrome morphs the whole frame around one step.
Scan the array
Walk left to right and remember the running maximum.
Installation
npx shadcn@latest add https://craftbits.dev/r/step-chrome.jsonUsage
import { StepChrome } from "@craft-bits/core";
<StepChrome
title="Scan the array"
index={1}
total={4}
footer={<NavRow onBack={prev} onNext={next} />}
>
<ArrayViz items={items} />
</StepChrome>Drop the footer for a header + body shell:
<StepChrome title="Compare each element" index={2} total={4}>
<CompareViz a={a} b={b} />
</StepChrome>Pass total={0} to hide the counter and the progress meter — useful for single-step intros that reuse the chrome:
<StepChrome title="Welcome" index={1} total={0}>
<Intro />
</StepChrome>Understanding the component
- Three stacked slots. The chrome is a flex column with
gap-4: header, body, optional footer. The header is itself a column with the title row above and the progress meter below. - Header row. Title fills the available space and truncates; the counter sits on the right in tabular-nums mono. When the step index changes, the counter cross-fades under
SPRINGS.smoothvia<AnimatePresence mode="popLayout" initial={false}>so the prior value slides out by 4px while the new one fades in from the opposite offset. - Progress meter. A 1px rail beneath the header eases its width to the new fraction with
SPRINGS.smooth. The rail carriesrole="progressbar"witharia-valuemin=1,aria-valuemax=total, andaria-valuenow=indexso assistive tech announces progress on every step. - Body slot. Children are rendered as-is in a
min-w-0column — the chrome owns no body layout beyond ensuring shrinkable content does not overflow the rounded card. - Footer slot. Optional. When omitted, the chrome ends at the body. When present, it renders inside a
<footer>with its own column gap so nav rows, hints, and status pills stack predictably. - Reduced motion. Under
prefers-reduced-motion: reducethe counter cross-fade and the meter width transition both short-circuit toduration: 0— the swap becomes instant and no perceived motion remains.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | required | Title of the current step. Plain string or any ReactNode. |
index | number | required | Current step number, 1-indexed. Clamped to 1..total when total is positive. |
total | number | required | Total step count. Pass 0 to hide the counter and progress meter. |
children | ReactNode | required | Body of the step — the visualization, prose, or interactive widget. |
footer | ReactNode | — | Optional footer slot — nav row, hint line, status pill. |
className | string | — | Merged onto the root <section> via cn(). |
Accessibility
- The root is a
<section>witharia-label="Step {index} of {total}"(omitted when total is 0) so assistive tech can announce the bounded region when the user lands on it. - The progress meter carries
role="progressbar",aria-valuenow,aria-valuemin=1,aria-valuemax=total, and anaria-labelthat includes the percent complete — assistive tech reads the new progress on every step change. - The counter span is marked
aria-hidden="true"because the progress meter already carries the accessible announcement, so the screen reader is not double-read on every swap. - Header markup uses an
<h3>for the title so the chrome slots into the surrounding heading outline without breaking the document tree. - Animation is transform + opacity + width only — no layout-thrashing properties.
- Under
prefers-reduced-motion: reduceboth the counter cross-fade and the meter width transition collapse toduration: 0— the swap becomes instant.
Credits
- Extracted from:
algoflashcards(src/lessons/primitives/chrome/StepChrome.tsx). The source shipped a compoundStepChrome.Root+.Bar+.Counter+.Dots+.Railwith a context-driven step list and per-step status. craft-bits' version collapses to a single shell — header (title + counter + progress meter) + body + optional footer — the surface most consumers actually reached for, without the compound API that drove the original token bloat.