Cookbook Layout

A multi-region application shell for product surfaces that need a sidebar beside a main column, a banner header on top, a sticky navigation rail along the bottom, and floating slots in the corners. Pure layout — every slot is an optional ReactNode, no hooks, no state, no scroll listeners. Generalised from the original recipe playground shell into something that fits dashboards, tutorials, settings panels, and onboarding flows.

Recipe

Slow-roast tomato confit

Halve the tomatoes lengthwise and arrange cut-side up on a wide oven tray.

Drizzle generously with olive oil and scatter thyme.

Customize
Surface
plain
sm
Regions

Installation

npx shadcn@latest add https://craftbits.dev/r/cookbook-layout.json

Usage

Drop content into the slot you want it in — every slot is optional. The shell renders a flexbox column (min-h-screen) with a body row that splits into sidebar + main at the lg: breakpoint.

import { CookbookLayout, PaneHeader, ProgressBar } from "@craft-bits/core";
 
<CookbookLayout
  topBar={<ProgressBar value={66} />}
  header={<PaneHeader title="Slow-roast tomato confit" />}
  sidebar={<IngredientPanel items={ingredients} />}
  bottomBar={<StepTimeline steps={steps} currentIndex={2} />}
  topRightSlot={<SoundToggle />}
  bottomRightSlot={<TimerTray />}
  main={<StepCard step={steps[2]} />}
/>

Pick which side the sidebar lives on at desktop widths — the mobile drawer always sits below the main column:

<CookbookLayout
  sidebarPosition="right"
  sidebarWidth="md"
  sidebar={<Notes />}
  main={<Article />}
/>

Overlay a celebration or modal without re-wiring the rest of the shell:

<CookbookLayout
  main={<StepCard />}
  overlay={done ? <CompletionCelebration /> : null}
/>

Understanding the component

  1. Pure slots, no controllers. The shell is a layout primitive — it takes ReactNode props and arranges them. State (current step, timers, sound prefs) lives outside in a controller component. This matches the original cookbook split where the layout rendered the grid and the playground owned the hooks.
  2. Three structural rows. Top to bottom: topBar, header, then the flex-1 body row, then bottomBar. The body splits into sidebar + main at the lg: breakpoint and stacks below it on narrower viewports. The original hard-wired the order; here it follows the sidebarPosition prop.
  3. Sidebar collapses to a drawer. Below lg: the sidebar drops beneath the main column and stretches full-width — desktop position (left or right) only changes the lg:order-* utility.
  4. Sticky bottom bar with backdrop blur. The bottom slot is position: sticky; bottom: 0 with a backdrop-filter wash keyed on --cb-bg. Z-index stays at 10 so modals and tooltips still win. Scrolled content reads through but stays legible.
  5. Floating corner slots. topRightSlot (z-50) and bottomRightSlot (z-40) use position: fixed so they survive any parent scroll container. The overlay slot (z-60) sits above everything for celebrations and modals.
  6. Two surface presets. plain paints --cb-bg (the default page canvas); muted swaps to --cb-bg-muted for a recessed shell — the default sidebar and bottom bar carry their own borders so the swap stays readable.
  7. Data-attributes for nested styling. The root carries data-cb-cookbook-layout, data-surface, data-sidebar, and data-sidebar-width so the consumer can target nested regions without re-deriving the props. Every region also tags itself with data-cb-cookbook-region so a single global rule can re-skin every instance.

Props

CookbookLayout

PropTypeDefaultDescription
topBarReactNodePinned to the top of the shell, above header. Progress strip, breadcrumb rail, banner.
headerReactNodeBanner / hero row between topBar and the body split. Usually a <PaneHeader>.
sidebarReactNodeOptional aside beside the main column at lg:. Drops below main on narrow viewports.
sidebarPosition'left' | 'right''left'Which side the sidebar renders on at desktop widths.
sidebarWidth'xs' | 'sm' | 'md''sm'Desktop sidebar track. xs = 16rem, sm = 20rem, md = 24rem.
mainReactNodeCentral column content. Wrapped in a max-w-3xl container with editorial padding.
bottomBarReactNodeSticky bottom rail with backdrop blur. Step timeline, footer nav, action bar.
topRightSlotReactNodeFloating fixed-position slot at the top-right (z-50). Sound toggle, kebab.
bottomRightSlotReactNodeFloating fixed-position slot at the bottom-right (z-40). Timer tray, FAB.
overlayReactNodeTop-of-stack slot (z-60) for modals / celebrations / full-screen blockers.
surface'plain' | 'muted''plain'Page background — --cb-bg or --cb-bg-muted.
classNamestringMerged onto the rendered root via cn().
...restOmit<HTMLAttributes<HTMLDivElement>, 'title'>Any other div attribute.

Accessibility

  • The root renders a div so callers stay free to nest the layout inside any landmark (<main>, <section>, page root). Children pick their own semantics: a <PaneHeader> for header, a <nav> for bottomBar, etc.
  • The sidebar slot is wrapped in an <aside> so assistive tech announces it as complementary content beside the main region.
  • No motion, no focus traps, no scroll listeners. Theme transitions flow from --cb-* tokens.
  • The sticky bottom bar uses supports-[backdrop-filter] so browsers without backdrop-filter get a slightly more opaque fallback — scrolled content never bleeds through.
  • Color contrast: every default surface paints --cb-bg / --cb-bg-muted with --cb-fg and --cb-fg-muted text. Both pass WCAG AA.

Credits

  • Extracted from: terminal-dreams (src/components/cookbook/CookbookLayout.tsx). The original was a fixed-shape cookbook playground shell with hard-wired ProgressBar, SoundToggle, StepTimeline, and TimerTray mounts plus cookbook-specific steps / currentStepIndex / onStepClick props and [var(--color-bg)] styling. craft-bits widens the API into a fully slotted layout (no project state, no internal renders), rewires onto --cb-* tokens, adds sidebarPosition / sidebarWidth / surface variants, and exposes the floating top-right and bottom-right slots as generic ReactNode props so consumers can drop in any indicator without forking the shell.