A vertical stack layout for long-form content — articles, blog posts, lesson pages, docs. Caps the body to a comfortable editorial measure, applies a consistent vertical rhythm between stacked children, and exposes optional header / footer / sidebar slots that sit outside the measure (so a hero band or pagination row can stretch wider than the prose). A sidebar prop switches the body into a 2-column grid for table-of-contents or note-margin layouts.
Essay
On the editorial measure
Long-form copy reads best at a comfortable line measure. Drag the width slider to feel the difference between sixty-five and eighty-eight characters per line.
The gap slider controls the vertical rhythm between siblings. Smaller values pack the page; larger values give chapter breaks more air.
Toggle the sidebar to see the body switch from a single column to a 2-column grid with the aside on the chosen side.
Wrap a long-form page in an ArticleStack. Drop any nodes you like inside — the measure cap and vertical rhythm flow to every child.
import { ArticleStack, PaneHeader, Prose } from "@craft-bits/core";<ArticleStack header={<PaneHeader title="On the editorial measure" />} footer={<nav>Next chapter →</nav>}> <Prose> <p>Long-form copy reads best at a comfortable line measure.</p> <h2>Section heading</h2> <p>The body column caps at a ch-based max-width.</p> </Prose></ArticleStack>
Switch to a 2-column layout with a sidebar — handy for a table of contents or sticky notes:
Landmark by default. Always renders an HTML5 article element so the container is announced as a self-contained piece of content. No role wrapping needed.
Three regions, one stack. The root is a vertical flex column with three slots: header (full width), the body (capped at width), and footer (full width). The body is where children flow.
width caps the measure. Body content is bounded by a ch-based max-width so lines stay between forty-five and seventy-five characters — the readability sweet spot. narrow is 65ch, default is 76ch, wide is 88ch. Header and footer ignore the cap.
gap drives vertical rhythm. Children stack with a CSS gap matching the body type scale — sm for dense notes, md for the editorial default, lg for long-form chapters.
Optional sidebar grid. When sidebar is provided, the body region switches from a single column to a 2-column grid: the main measure stays at width, the sidebar occupies an auto track. On narrow viewports (below the md breakpoint) the grid collapses to one column and the sidebar stacks below the body.
sidebarPosition picks the side. right (default) follows the editorial convention; left keeps a ToC in the reader's primary scan path.
Data-attributes for nested styling. The root carries data-cb-article-stack, data-width, data-gap, and data-sidebar so the consumer can target nested regions without re-deriving props.
Props
ArticleStack
Prop
Type
Default
Description
header
ReactNode
—
Top region rendered above the stack. Ignores the body measure cap.
footer
ReactNode
—
Bottom region rendered below the stack. Ignores the body measure cap.
sidebar
ReactNode
—
Optional aside slot. Switches the body into a 2-column grid.
sidebarPosition
'left' | 'right'
'right'
Which side the sidebar renders on. Collapses below the body on narrow viewports.
width
'narrow' | 'default' | 'wide'
'default'
Maximum line measure (65ch / 76ch / 88ch).
gap
'sm' | 'md' | 'lg'
'md'
Vertical rhythm between stacked children.
className
string
—
Merged onto the rendered article via cn().
...rest
Omit<HTMLAttributes<HTMLElement>, 'title'>
—
Any other article attribute.
Accessibility
Root renders as an HTML5 article element — screen readers expose it as a self-contained piece of content.
The sidebar slot renders inside an aside so assistive tech announces it as complementary content related to the article, not the page.
No motion, no focus traps. Theme transitions are limited to colour from the surrounding cb-* tokens.
Color contrast: body uses cb-fg, secondary regions use cb-fg-muted — both pass WCAG AA against every default surface.
Credits
Extracted from: algoflashcards (src/lessons/primitives/chrome/ArticleStack.tsx). The original was a phase-tracking renderer with built-in PhaseRibbon integration, scroll-into-view side effects, and a LessonDisplayMode context dependency. craft-bits keeps the editorial intent — a stacked long-form layout with a capped measure — and strips the project-specific phase plumbing. The result is a generic editorial container with optional header / footer / sidebar slots, three width presets, and three gap presets. No motion, no client-side state.