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.
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.
Installation
npx shadcn@latest add https://craftbits.dev/r/cookbook-layout.jsonUsage
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
- 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.
- Three structural rows. Top to bottom:
topBar,header, then the flex-1 body row, thenbottomBar. The body splits intosidebar+mainat thelg:breakpoint and stacks below it on narrower viewports. The original hard-wired the order; here it follows thesidebarPositionprop. - 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 thelg:order-*utility. - Sticky bottom bar with backdrop blur. The bottom slot is
position: sticky; bottom: 0with 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. - Floating corner slots.
topRightSlot(z-50) andbottomRightSlot(z-40) useposition: fixedso they survive any parent scroll container. Theoverlayslot (z-60) sits above everything for celebrations and modals. - Two surface presets.
plainpaints--cb-bg(the default page canvas);mutedswaps to--cb-bg-mutedfor a recessed shell — the default sidebar and bottom bar carry their own borders so the swap stays readable. - Data-attributes for nested styling. The root carries
data-cb-cookbook-layout,data-surface,data-sidebar, anddata-sidebar-widthso the consumer can target nested regions without re-deriving the props. Every region also tags itself withdata-cb-cookbook-regionso a single global rule can re-skin every instance.
Props
CookbookLayout
| Prop | Type | Default | Description |
|---|---|---|---|
topBar | ReactNode | — | Pinned to the top of the shell, above header. Progress strip, breadcrumb rail, banner. |
header | ReactNode | — | Banner / hero row between topBar and the body split. Usually a <PaneHeader>. |
sidebar | ReactNode | — | Optional 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. |
main | ReactNode | — | Central column content. Wrapped in a max-w-3xl container with editorial padding. |
bottomBar | ReactNode | — | Sticky bottom rail with backdrop blur. Step timeline, footer nav, action bar. |
topRightSlot | ReactNode | — | Floating fixed-position slot at the top-right (z-50). Sound toggle, kebab. |
bottomRightSlot | ReactNode | — | Floating fixed-position slot at the bottom-right (z-40). Timer tray, FAB. |
overlay | ReactNode | — | Top-of-stack slot (z-60) for modals / celebrations / full-screen blockers. |
surface | 'plain' | 'muted' | 'plain' | Page background — --cb-bg or --cb-bg-muted. |
className | string | — | Merged onto the rendered root via cn(). |
...rest | Omit<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>forheader, a<nav>forbottomBar, 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-mutedwith--cb-fgand--cb-fg-mutedtext. 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-specificsteps/currentStepIndex/onStepClickprops 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, addssidebarPosition/sidebarWidth/surfacevariants, 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.