Page
A layout primitive that owns the vertical rhythm inside the main column.
Basic
The .page flow is a flex column whose gap spaces a .page__header and a series of .page__section siblings, so children carry no outer margin (the reboot already zeroes headings and paragraphs). The header pairs a .page__headline (title + description) with a trailing .page__action; sections repeat the shape at a tighter scale. Title and section-title carry opinionated cadences so you drop the recurring heading utility. A header with a title, a muted description, and an action, followed by a section with its own header.
The page wrapper
Drop a .page inside the main column. .page__header carries the title and primary actions and sits above .page__body, the flow that holds the sections. The header's bottom margin supplies the rhythm between them.
Page header
.page__header is a flex row with a .page__headline group on the leading edge and a .page__action slot on the trailing edge. The heading group stacks a .page__title with an optional .page__description; the action slot takes any button row.
Without actions
Drop the action slot. The title block sits alone.
Sections
.page__section is a flex-column for each block inside the page. The .page parent supplies the gap between sections; each section supplies its own inner gap via --page-section-inner-gap. Pair .page__section-title with .page__section-description for muted helper copy beneath it, the section-scale counterpart to .page__description.
With section header + actions
For sections that need their own action row, wrap the title in .page__section-header and reuse .page__action as the slot. Same flex recipe at a tighter scale.
Content width
.page is unopinionated about width. Wrap it (or set its own width on a parent) to clamp the content column.
- Full width (
.container-fluidor no container) for dashboards, tables, anything dense. - Breakpoint-clamped (
.container) for marketing pages, or anything that should match the standard column widths. - Custom max-width (40 to 60rem) for settings forms, articles, anything text-heavy where long lines hurt readability.
Nesting a container
Putting .container on the .page element itself, as above, keeps the sections as direct children, so the row gap still spaces them. When the container has to be a separate wrapper, it becomes the only child of .page and the sections inside it fall outside the gap. Move the flow into .page__body. It owns the same section rhythm, so a container can sit between the frame and the content without flattening the spacing.
.page__body reads the same --page-section-gap as .page, so a gap override set on the page still reaches the nested sections. Reach for it only when a container nests inside the page; the plain .page already flows its own direct children.
Putting it together
Page header, sections, and a section with its own header, all inside one .page that supplies the rhythm.
Customization
These variables retune the flow and the heading cadences. Override on the root or any wrapper.
| Variable | Use |
|---|---|
--page-section-gap | Gap between the header and each section (cascades to .page__body) |
--page-header-gap | Gap between the headline and the actions |
--page-heading-gap | Gap between a title and its description |
--page-action-gap | Gap between action buttons |
--page-section-inner-gap | Gap between a section header and its content |
--page-section-header-gap | Gap inside a section header |
--page-title-font-size / -title-font-weight | Page title type |
--page-description-font-size / -description-color | Page description type |
--page-section-title-font-size / -section-title-font-weight | Section title type |
--page-section-description-font-size / -section-description-color | Section description type |