Recipe Header
A page header for article, recipe, or guide screens. Renders a large serif title with an optional eyebrow kicker above it, a short description, a freeform row of meta chips, and an optional trailing actions slot. Pure CSS — no motion, no client state.
Bread · 04
Sourdough, no fuss
A 24-hour loaf with three folds, one bake, and no special equipment beyond a Dutch oven.
45 min2 loavesIntermediateveganmake-ahead
Customize
Layout
comfortable
start
Slots
Installation
npx shadcn@latest add https://craftbits.dev/r/recipe-header.jsonUsage
RecipeHeader is a Radix-style compound. The root paints the header bar; RecipeHeader.Meta is a single chip in the meta row; RecipeHeader.Tag is a pill-shaped tag for taxonomy values.
import { RecipeHeader, ClockIcon, ServingsIcon } from "@craft-bits/core";
<RecipeHeader
title="Sourdough, no fuss"
eyebrow="Bread · 04"
description="A 24-hour loaf with three folds and one bake."
meta={
<>
<RecipeHeader.Meta icon={<ClockIcon className="h-4 w-4" />}>
45 min
</RecipeHeader.Meta>
<RecipeHeader.Meta icon={<ServingsIcon className="h-4 w-4" />}>
2 loaves
</RecipeHeader.Meta>
<RecipeHeader.Meta>Intermediate</RecipeHeader.Meta>
<RecipeHeader.Tag>vegan</RecipeHeader.Tag>
</>
}
/>Add a trailing actions slot — primary actions, share buttons, bookmarks, etc.:
<RecipeHeader
title="Quicksort, visualised"
eyebrow="Algorithms"
description="Watch the partition step shuffle in real time."
actions={<button>Bookmark</button>}
/>Skip the eyebrow, description, or meta row by omitting the corresponding prop — every part is optional except title.
Understanding the component
- Compound parts.
RecipeHeaderis the bar;RecipeHeader.Metais one chip;RecipeHeader.Tagis a pill tag. Composing the meta row rather than passing an array of items means each chip owns its own icon and formatting. - Title is a heading-1. Every page header is a top-level landmark — assistive tech announces the title as the document heading.
- Eyebrow above, description below. The eyebrow is an uppercase mono kicker — short, label-like. The description is a body paragraph in muted ink, set with
text-wrap: prettyso line endings break gracefully. - Meta row is freeform. Drop any nodes you want —
RecipeHeader.Metachips, plain text, custom badges. The row isflex-wrapwith consistent gaps; it stays single-line on wide screens and stacks on narrow. - Tabular numerals on meta. Each
RecipeHeader.Metaenablesfont-variant-numeric: tabular-numsso numeric values align column-cleanly when stacked. - Actions slot pins right. When
actionsis present, the inner column switches toitems-start justify-betweenso the actions ride the top-right corner. - Density variants.
compact,comfortable, andspacioustune the vertical padding for dense dashboards vs. magazine-style spreads. - Data hooks. The root carries
data-cb-recipe-header,data-density, anddata-alignso consumers can target the header from a wrapping layout without re-deriving props.
Variants
Bare — title only
<RecipeHeader title="Quick read" />With actions
<RecipeHeader
title="API reference"
eyebrow="v3.2"
actions={<button>Edit on GitHub</button>}
/>Centered, spacious
<RecipeHeader
align="center"
density="spacious"
title="Welcome"
description="The brief, the bar, the bits."
/>Compact (dashboard-style)
<RecipeHeader
density="compact"
title="Run 0149"
eyebrow="Experiments"
meta={<RecipeHeader.Meta>12s ago</RecipeHeader.Meta>}
/>Props
RecipeHeader
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | — | Required. Primary heading. |
eyebrow | ReactNode | — | Small kicker label above the title. |
description | ReactNode | — | Short paragraph below the title. |
meta | ReactNode | — | Slot for a freeform row of meta chips. |
actions | ReactNode | — | Trailing slot — usually primary actions. |
density | 'compact' | 'comfortable' | 'spacious' | 'comfortable' | Vertical padding scale. |
align | 'start' | 'center' | 'start' | Text alignment of the title column. |
innerClassName | string | — | Override classes on the centered inner column. |
className | string | — | Merged onto the rendered header via cn(). |
...rest | HTMLAttributes<HTMLElement> | — | Any other header attribute. |
RecipeHeader.Meta
| Prop | Type | Default | Description |
|---|---|---|---|
icon | ReactNode | — | Optional leading 16px icon. |
className | string | — | Merged onto the rendered span via cn(). |
...rest | HTMLAttributes<HTMLSpanElement> | — | Any other span attribute. |
RecipeHeader.Tag
| Prop | Type | Default | Description |
|---|---|---|---|
tone | 'accent' | 'neutral' | 'muted' | 'accent' | Pill color tone. |
className | string | — | Merged onto the rendered span via cn(). |
...rest | HTMLAttributes<HTMLSpanElement> | — | Any other span attribute. |
Accessibility
- The root renders a header landmark and the title is a heading-1 — screen readers announce the page heading correctly.
ClockIconandServingsIcondefault toaria-hidden="true"; the surroundingRecipeHeader.Metatext supplies the accessible name.- Color contrast: the title uses
--cb-fg, the description uses--cb-fg-muted, the meta chips use--cb-fg-muted/--cb-fg-subtle. All pass WCAG AA on every default surface. - No motion is applied. Reduced-motion respect is the responsibility of any custom action button passed into the
actionsslot. - The title uses
text-wrap: balanceso multi-line headings break evenly; the description usestext-wrap: prettyto avoid orphan words.
Credits
- Extracted from:
terminal-dreams(src/components/cookbook/RecipeHeader.tsx). The original was tightly coupled to aCookbookRecipeshape (recipe.meta.totalTime,recipe.meta.servings,recipe.meta.difficulty,recipe.meta.cuisine,recipe.meta.tags) and rendered a siblingBreadcrumbBarinside the same fragment. craft-bits strips every cookbook-specific concern and keeps the underlying intent — a page header for an article-style screen with title, eyebrow, description, meta chips, and actions. The compoundMeta/Tagshape is new; the original inlined every piece.