Brik Design System
Build Standards

Page Grid

The horizontal frame around every Brik page — the width-container recipe, the canonical page-inset token, the one content-width ladder that caps both bands and text columns, and the rule that a column has one width and one left edge.

Content Rhythm governs the vertical axis; this page governs the horizontal one. Every content band on a Brik page — a marketing section's container, a header, a footer, a consumer site's nav — is framed the same way: a --content-width-* band, centred, inset from the viewport edge by the canonical page inset. One recipe, so BDS sections and consumer containers stay flush-aligned at every breakpoint (ADR-025).

The width-container recipe

Three declarations, always together:

.my-container {
  max-width: var(--bds-content-width-xl);   /* the band */
  margin-inline: auto;                  /* centred */
  padding-inline: var(--bds-page-inset);    /* the page-edge inset */
}

--content-width-xl (1280px) is the default band — the value the section shell, SiteHeader, Footer, and the brikdesigns nav all resolve to. Narrower bands are a per-role choice from the width roles table, not a new number.

The lint-page-grid gate fails any container rule whose padding-inline bypasses --page-inset — a container on an ad-hoc inset drifts out of alignment with every other band on the page.

Page inset vs column gutter

"Gutter" names two different relationships. Only the page edge gets a dedicated token; space between columns is already governed by the component rhythm map.

SenseRelationshipMechanism
Page insetViewport edge ↔ content bandpadding-inline: var(--bds-page-inset) on the container
Column gutterSibling ↔ sibling in a Grid or ClusterThe primitive's gap, from the --gap-* scale

This is the gap-vs-padding rule from Content Rhythm applied horizontally: gap is between siblings, padding is the edge.

--page-inset (renamed from --gutter-page; the old name survives as a deprecated alias) is part of the layout tier: it scales with the viewport — 16px at 320, 32px at 768, 96px at 1440 and above — and does not change with spacing modes (ADR-042; its old density ladder is gone). The fluid value lives only on this one BDS token. Never re-declare it with a site-level clamp(): brikdesigns once did, and its nav drifted from its sections because two tokens disagreed — one shared token can't. In TS, import pageInset from @bds-tokens rather than writing the var() string inline.

Content width roles

One ladder caps everything horizontal, and each rung carries exactly one role — stated in its name. A -text- rung caps a text column; a bare rung sizes a band (ADR-041).

TokenWidthCapsRole
--bds-content-width-text-xs448pxtextShort intro — a single-line eyebrow + title
--bds-content-width-text-sm600pxtextSection intro — title + description (the default)
--bds-content-width-text-md720pxtextLong-form prose, rich-text body
--bds-content-width-sm640pxbandNarrow band, focused forms
--bds-content-width-md800pxbandStandard text-led body sections
--bds-content-width-lg1024pxbandFeature grids, CTA bands
--bds-content-width-xl1280pxbandThe page band — hero, header, footer, most sections
--bds-content-width-full100%bandFull-bleed; prefer an explicit width

The text rungs interleave numerically with the band rungs (600 < 640 < 720 < 800). That is expected and harmless: pick a rung by its role, which the name states — never by sorting the ladder.

There was a second --measure-* family in ch for the text column; ADR-041 retired it into the -text- rungs above, at the px widths it already rendered (the old names ship one more minor as deprecated aliases, then drop). Nothing in BDS scales body type (--body-md is a fixed 16px, only --display-fluid-* clamps, and [data-mode-spacing] never touches font-size), so ch never tracked anything — while two siblings sharing one ch cap at different type sizes resolved to two different pixel widths. Widths are static across spacing modes either way: a readable line length is a typographic constant, not a density knob.

These are content constraints, not breakpoints — even where the pixel values coincide. For media queries use the mediaQueries export from @bds-tokens (or breakpoints for a raw width); CSS custom properties cannot appear inside @media conditions, so the --breakpoint-* CSS variables are reference-only.

There is no responsive-spacing axis: density modes are the only spacing modulation in BDS. Do not introduce breakpoint-varying gutters or gaps.

The column rule

A column has one content width and one left edge. The element that OWNS the column sets the width; blocks nested inside it never re-cap themselves. Two caps in one column is the defect, not the fix.

This is the rule most often missed. A section's content container picks one rung and every block inside it inherits that column — a nested prose block, a hero block, a media block do not each declare their own max-width. Two caps in one column give the eye two different left edges and read as broken alignment, whichever tokens they use.

/* ✅ one owner, one cap */
.split__content { max-width: var(--bds-content-width-text-sm); }
.split__content > .prose { /* no max-width — inherits the column */ }

/* ❌ two caps, two left edges */
.split__content { max-width: var(--bds-content-width-text-sm); }
.split__content > .hero  { max-width: var(--bds-content-width-sm); }
.split__content > .prose { max-width: var(--bds-content-width-text-md); }

If a block genuinely needs a different width from its siblings, it is not in that column — give it its own column, or restructure. Don't re-cap inside one.

Text column vs band

A band width is not a text width. --content-width-xl (1280px) sizes the band; a title or description spanning the full 1280px is unreadable. The text column is the readable column inside the band — a narrower cap applied to the text group, not to the container.

Same ladder, two jobs — keep the jobs separate:

SizesRungMechanism
BandThe container / section width--content-width-sm…fullmax-width + margin-inline: auto on the container
Text columnThe text column inside it--content-width-text-*max-width on the text group — usually via SectionHeader

SectionHeader

Don't hand-roll the cap. A centered section intro — eyebrow / title / description above a card grid, a CTA band, a hero — is a SectionHeader: it composes ContentBlock for the slot rhythm (ADR-023) and adds only the measure + centering — the concern ContentBlock deliberately does not own.

<SectionHeader
  title="What we do"
  description="From branding to websites to behind-the-scenes systems…"
/>
// centered · measure-md (--bds-content-width-text-sm, 600px) · <h2> title at heading-lg — nothing to override

measure (sm / md / lg, default md) picks the cap; align (center / start, default center) picks whether the column centers in the band or anchors to its start. SectionHeader never sets a band width — drop it inside the section's width-container and it caps the text within. This is the single replacement for the ad-hoc measures a section header used to be written with (600px, 700px, width: 70%, max-width: none, a reused band rung).

A text column is not a band. Never cap a section intro with a band rung (--content-width-sm…full — those size the container) or with a raw px / percentage. Reach for SectionHeader — or a --content-width-text-* rung directly only when hand-rolling a text group SectionHeader doesn't cover. Reading a band rung's role as "prose" is what shipped brikdesigns#1827; ADR-041 put the role in the name so the table can only be read one way.

Where the recipe is set

Like the rhythm scale, you rarely write the recipe by hand — it is baked into the layer that owns each surface.

  • Marketing sections → .bds-blueprint-section__container (ADR-021). Override band or inset via the section's hooks, never by hand-rolling a sibling container:
.bds-cta__container {
  --bds-blueprint-section-content-width: var(--bds-content-width-lg);
  --bds-blueprint-section-padding-inline: var(--bds-padding-xl);
}
  • Product apps → the Page primitive; its padding prop sets the shell inset (lg is the page gutter).
  • Full-bleed backgrounds with constrained content → Footer's containerMaxWidth shape: the background spans the viewport while padding-inline: max(var(--page-inset), calc((100% - var(--content-width-xl)) / 2)) centres the content band.
  • Section vertical band → padding-block: var(--section-padding-block) — 64px at 320, 80px at 768, 104px at 1440 and above (ADR-042; it replaces ADR-021's 7vw clamp). A page is a stack of full-bleed sections; the vertical band is --section-padding-block, the horizontal band is this recipe.
  • Content Rhythm — the vertical axis this page mirrors.
  • Page Structure — the HTML skeleton these containers live in.
  • Composition Layers — the Blueprint → Layout → Container → Block or Control → Component model.
  • Spacing — the --padding-* scale, the density modes that modulate it, and the layout tier that sets the page inset.
  • Size — the --content-width-* ladder's token catalog.

On this page

💬