Step Widgets

A thin shell for stepwise interactive lessons. The rail at the top names each step; the body below renders the active step's widget — a slider, a diagram, a markup walkthrough, anything. Controlled or uncontrolled; full tablist semantics; keyboard navigation built in.

Preview

AVIF ships roughly half the bytes of JPEG at matched quality. WebP is the universally-supported fallback. JPEG remains the safety net for the last <img> in a <picture>.

Customize

Installation

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

Usage

import { StepWidgets } from "@craft-bits/core";
 
<StepWidgets
  steps={[
    { id: "format", label: "Format", content: <FormatLandscape /> },
    { id: "quality", label: "Quality", content: <CompressionQuality /> },
    { id: "srcset", label: "Srcset", content: <SrcsetBuilder /> },
  ]}
/>

Controlled mode — drive activeStep from outside:

const [step, setStep] = useState("format");
 
<StepWidgets
  activeStep={step}
  onActiveStepChange={(id) => setStep(id)}
  steps={[
    { id: "format", label: "Format", content: <FormatLandscape /> },
    { id: "quality", label: "Quality", content: <CompressionQuality /> },
  ]}
/>

Anatomy

  • Step rail — a role="tablist" row (or column) of buttons. Each shows a numbered accent pill, the step's label, and an optional description slot. The active trigger picks up an accent-muted fill and a 2px accent underline.
  • Step body — a single role="tabpanel". Either pass content per step or pass children to the whole component for a static panel.
  • Orientationhorizontal (default, rail on top) or vertical (rail on the left at md+).

Props

PropTypeDefaultDescription
stepsStepWidgetsStep[]requiredOrdered step descriptors.
activeStepstringControlled active step id.
defaultActiveStepstringfirst step idInitial step in uncontrolled mode.
onActiveStepChange(id, index) => voidFired on tab change.
orientation'horizontal' | 'vertical''horizontal'Rail layout.
renderTrigger(args) => ReactNodeOverride the trigger element.
childrenReactNodeStatic body, replaces the per-step content.
classNamestringMerged onto the root via cn().

StepWidgetsStep

FieldTypeDescription
idstringStable identifier.
labelReactNodeVisible name on the rail.
descriptionReactNodeOptional sub-label under the name.
contentReactNode | (index) => ReactNodeBody for this step.

Accessibility

  • The rail is a role="tablist" with aria-orientation set to match the layout.
  • Each trigger is a role="tab" with aria-selected, aria-controls, and roving tabindex.
  • The body is a role="tabpanel" linked by aria-labelledby to the active trigger.
  • Arrow keys move between triggers; Home and End jump to the first or last step.

Credits

  • Extracted from: terminal-dreams (src/components/frontend-design/perf-images/ui/StepWidgets.tsx). The original was a bundle of six lesson-specific widgets; this primitive keeps the stepwise shell and pushes every domain-specific concern back to the consumer's panel content.