Cookbook Playground
A stepped interactive recipe stepper. Renders the active step as a card (title + description + a content slot), with Previous / Next buttons, a thin progress bar, a numbered step rail at the bottom, and an optional celebration slot that mounts when the user reaches the final step. Controlled or uncontrolled.
Slow-roast tomato confit
Tap Next to advance, or pick any numbered step to jump.
Progress1/4
Prep the tomatoes
Set up the tray so the oven step is hands-off.
Halve the tomatoes lengthwise. Arrange cut-side up on a wide oven tray and tuck whole peeled garlic cloves between them.
Customize
Regions
Installation
npx shadcn@latest add https://craftbits.dev/r/cookbook-playground.jsonUsage
import { CookbookPlayground } from "@craft-bits/core";
<CookbookPlayground
title="Slow-roast tomato confit"
steps={[
{ id: "prep", title: "Prep the tomatoes", content: "Halve and tray." },
{ id: "season", title: "Season generously", content: "Oil, salt, thyme." },
{ id: "roast", title: "Slow roast at 140C", content: "2 to 2.5 hours." },
{ id: "finish", title: "Rest and serve", content: "On toast or in pasta." },
]}
/>Controlled mode — drive activeStep from outside so the step index can feed an external timer, scrollytelling rig, or URL hash:
const [step, setStep] = useState(0);
<CookbookPlayground
steps={STEPS}
activeStep={step}
onActiveStepChange={setStep}
onComplete={(last) => console.log("done", last)}
/>Drop a celebration onto the final step without re-wiring the controller:
<CookbookPlayground
steps={STEPS}
celebration={<CompletionCelebration totalTime={2700} />}
/>Anatomy
- Header. Optional
title(rendered with thecb-labelstyle) and anintrosub-line. Omit both for a chromeless playground. - Progress. A label + counter row above a thin animated bar that fills with the
SPRINGS.snaptoken. Hide withhideProgress. - Step card. The active step's
title,description, andcontentslot. A numbered pill on the left shows the 1-based index. The card crossfades when the step changes via<AnimatePresence>in modewait. - Controls. A Previous / Next button row. The Next button changes its label to the
doneLabel(Mark complete) when the user is on the final step. Hide withhideControls. - Step rail. A row of numbered dots at the bottom — active (filled accent), done (subdued accent), pending (muted). Tap any dot to jump. Hide with
hideStepRail. - Celebration. An optional slot that mounts under the playground when the active step is the last one, animated in with the same snap spring.
Understanding the component
- Slot-driven steps, no project chrome. The original cookbook controller pulled in
useStepNavigation,useIngredientTracking,useSound, aCookbookTimerProvider, aRecipeHeader, anIngredientPanel, aStepCard, and aCompletionCelebration. This rewrite keeps only the active step + controls + rail behaviour. Timers, ingredient highlighting, chef tips, and sound effects compose inside thecontentslot of each step — the playground itself is purely a stepper. - Controlled + uncontrolled. Provide
activeStepwithonActiveStepChangeto drive from outside (URL hash, parent scrollytelling, external simulator). Omit both and the playground manages internal state seeded bydefaultActiveStep. Out-of-range values are clamped silently into the valid index range. - Completion fires once per arrival.
onCompletefires when the user lands on the final step. It re-arms once the user navigates back, so re-reaching the last step fires it again — useful for celebration replay. The Next button on the last step also callsonCompleteso consumers can wire it to a custom finish action. - Motion. The step card crossfades on
idchange via Motion'sAnimatePresencewith modewait. The progress bar fills via a width animation on theSPRINGS.snaptoken. Every animation short-circuits underprefers-reduced-motion.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
steps | CookbookPlaygroundStep[] | required | All steps in display order. |
activeStep | number | — | Controlled active-step index. |
defaultActiveStep | number | 0 | Initial active step in uncontrolled mode. |
onActiveStepChange | (next: number) => void | — | Fires on Next / Previous / rail click. |
intro | ReactNode | — | Sub-headline under the playground title. |
title | ReactNode | — | Optional heading above the playground. |
headingAs | 'h2' | 'h3' | 'h4' | 'h3' | Tag for the title element. |
nextLabel | ReactNode | 'Next step' | Label on the Next button. |
previousLabel | ReactNode | 'Previous' | Label on the Previous button. |
doneLabel | ReactNode | 'Mark complete' | Label on the Next button when the user is on the last step. |
onComplete | (lastIndex: number) => void | — | Fires when the user reaches or finishes the final step. |
hideStepRail | boolean | false | Hide the numbered step rail. |
hideProgress | boolean | false | Hide the linear progress bar. |
hideControls | boolean | false | Hide the Previous / Next button row. |
celebration | ReactNode | — | Slot mounted under the playground on the final step. |
stepRailLabel | string | 'Recipe steps' | ARIA label for the step rail. |
className | string | — | Merged onto the root via cn(). |
CookbookPlaygroundStep
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier — used as the React key and on data-cb-step-id. |
title | ReactNode | Short heading rendered above the step body. |
description | ReactNode | Optional one-line subtitle under the title. |
content | ReactNode | Body of the step — instructions, embedded widgets, code blocks. |
Accessibility
- The root is a
<section>withdata-cb-edu="cookbook-playground",data-cb-active-step,data-cb-total-steps, anddata-cb-completeso consumers can extend per-state styling without re-deriving the props. - The progress bar carries
role="progressbar"witharia-valuenow/aria-valuemin/aria-valuemaxand anaria-label. - The step card pill (the numbered chip) is
aria-hiddenbecause the heading already conveys the step index. The numeric counter row uses anaria-labellikeStep 2 of 4so the headline is conveyed without color. - The step rail is a
<nav>witharia-label="Recipe steps". Each dot exposesaria-current="step"when active andaria-labellikeGo to step 3. - The Next button toggles its
aria-labelbetweenNext stepandMark recipe completeso screen readers always know which action will fire. - The Previous button uses
disabledplus anaria-labelso it announces correctly when there is no earlier step. - All animations are opacity + transform only and short-circuit under
prefers-reduced-motion.
Credits
- Extracted from:
terminal-dreams(src/components/cookbook/CookbookPlayground.tsx). The original was a controller that wired four cookbook hooks (useStepNavigation,useIngredientTracking,useSound,CookbookTimerProvider), required aCookbookRecipeshape withsteps+ingredients+meta.totalTime, and rendered fixed<RecipeHeader>/<StepCard>/<IngredientPanel>/<CompletionCelebration>children inside a hard-wired<CookbookLayout>. craft-bits keeps only the step-sequencer behaviour — controlled + uncontrolled active step, deferredonComplete, slot-driven step card — so the same component drives recipes, tutorials, wizards, and explainers without dragging the timer / ingredient / sound stack along with it.