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.

Tap continue to advance.

Customize
Step
1 / 4
Chrome

Installation

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

Usage

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

  1. 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.
  2. 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.smooth via <AnimatePresence mode="popLayout" initial={false}> so the prior value slides out by 4px while the new one fades in from the opposite offset.
  3. Progress meter. A 1px rail beneath the header eases its width to the new fraction with SPRINGS.smooth. The rail carries role="progressbar" with aria-valuemin=1, aria-valuemax=total, and aria-valuenow=index so assistive tech announces progress on every step.
  4. Body slot. Children are rendered as-is in a min-w-0 column — the chrome owns no body layout beyond ensuring shrinkable content does not overflow the rounded card.
  5. 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.
  6. Reduced motion. Under prefers-reduced-motion: reduce the counter cross-fade and the meter width transition both short-circuit to duration: 0 — the swap becomes instant and no perceived motion remains.

Props

PropTypeDefaultDescription
titleReactNoderequiredTitle of the current step. Plain string or any ReactNode.
indexnumberrequiredCurrent step number, 1-indexed. Clamped to 1..total when total is positive.
totalnumberrequiredTotal step count. Pass 0 to hide the counter and progress meter.
childrenReactNoderequiredBody of the step — the visualization, prose, or interactive widget.
footerReactNodeOptional footer slot — nav row, hint line, status pill.
classNamestringMerged onto the root <section> via cn().

Accessibility

  • The root is a <section> with aria-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 an aria-label that 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: reduce both the counter cross-fade and the meter width transition collapse to duration: 0 — the swap becomes instant.

Credits

  • Extracted from: algoflashcards (src/lessons/primitives/chrome/StepChrome.tsx). The source shipped a compound StepChrome.Root + .Bar + .Counter + .Dots + .Rail with 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.