Layout
Two-tier model
The layout system operates in two tiers. Tier 1 is the page layout — the top-level structural skeleton that defines how the page is divided into regions (header, main content, sidebar, footer). Tier 2 is compositions — layout primitives such as l-stack, l-cluster, and l-grid that operate within those regions.
Two rules govern this model:
- Every page picks exactly one page layout class.
- Page layouts never nest inside one another.
Compositions may nest freely within regions. Page layouts may not.
Page layouts
l-page-content
A centered single-column layout. The main content area is constrained to a readable line length and centered in the viewport. There is no sidebar.
When to use: Use for focused, reading-heavy pages — documentation, articles, form completion flows — where a sidebar would distract from the primary task. Most catalog pages use this layout.
Apply the class to the main element directly. The page-level site chrome (header, footer) lives outside the page layout.
<main class="l-page-content">
<h1>Title</h1>
<p>Body.</p>
<figure class="l-feature">Wider element</figure>
</main>l-page-sidebar-start
A two-column layout with a navigation sidebar on the left (start) and main content on the right. At narrow viewports the sidebar collapses and may be toggled open.
When to use: Use when the page belongs to a section with persistent secondary navigation — catalog pages, multi-step wizards with a step list, or admin dashboards. The sidebar communicates hierarchy and position within a larger structure.
Apply the class to a wrapper div with exactly two children: .l-page-sidebar and .l-page-main. The page-level site chrome lives outside the wrapper.
<div class="l-page-sidebar-start">
<aside class="l-page-sidebar">
<nav>...</nav>
</aside>
<main class="l-page-main">
<h1>Title</h1>
<p>Body.</p>
<figure class="l-feature">Wider element</figure>
</main>
</div>l-page-sidebar-end
A two-column layout with a contextual sidebar on the right (end) and main content on the left. At narrow viewports the sidebar collapses below the main content.
When to use: Use for contextual supplemental content that enhances but does not drive navigation — help text, related links, table of contents for the current page, or a persistent preview panel. The content reads left-to-right with the sidebar as an aside.
Same wrapper pattern as l-page-sidebar-start: a div with .l-page-main first and .l-page-sidebar second.
<div class="l-page-sidebar-end">
<main class="l-page-main">
<h1>Title</h1>
<p>Body.</p>
<figure class="l-feature">Wider element</figure>
</main>
<aside class="l-page-sidebar">
<nav>...</nav>
</aside>
</div>Breakout tracks
Within a page layout, content can break out of the default readable line-length constraint using width track classes. Breakout classes (.l-popout, .l-feature, .l-full) apply to direct children of .l-page-content or .l-page-main. For the single-column l-page-content layout, that is the page layout element itself. For the sidebar layouts, that is the inner .l-page-main inside the .l-page-sidebar-start or .l-page-sidebar-end wrapper.
- content (default) — Stays within the readable measure. No extra class needed.
l-popout— Adds up to ~2rem on each side beyond the content track; good for wide tables and card groups.l-feature— Adds up to ~5rem on each side beyond the content track; useful for hero sections and wide images.l-full— Edge-to-edge across the full page width with no horizontal padding.
Width variants are available via the data-width attribute on .l-page-content or .l-page-main. It overrides --flex-content-default so the content-track width cascades to all descendants: data-width="narrow" tightens the measure for short-form content; data-width="wide" relaxes it for data-dense views.
Container contract
The page layouts define named CSS containers that components can query via @container. The page-content named container is set up on every page layout — it lives on .l-page-content and on .l-page-main inside sidebar layouts. The page-sidebar named container exists only when one of the sidebar layouts is used (it is set up on .l-page-sidebar-start > .l-page-sidebar and .l-page-sidebar-end > .l-page-sidebar).
/* Adapt to the content region width, not the viewport */
@container page-content (max-width: 600px) {
.my-component { flex-direction: column; }
}This means a component placed in a narrower content region behaves correctly regardless of viewport width — it responds to its actual container.
The l-switcher composition
The l-switcher composition is a responsive row that automatically collapses to a vertical stack when the container is too narrow to display items side by side. It is the preferred pattern for form field groups where two or three short fields logically belong on the same row.
<div class="l-switcher" style="--threshold: 30rem;">
<div>
<label for="first-name">First name</label>
<input id="first-name" type="text" />
</div>
<div>
<label for="last-name">Last name</label>
<input id="last-name" type="text" />
</div>
</div>The threshold at which the switcher collapses is controlled by the --threshold custom property (default: 30rem). Gap between items is controlled by --switcher-space (defaults to --flex-space-md). Uses the Every Layout flexbox trick — no media queries or container queries required. Children switch modes based on the switcher's own available width, which makes it safe to use inside any parent layout.
What this system does NOT provide
To keep the layout system predictable and prevent misuse, the following patterns are intentionally absent:
- A 12-column grid system
- Breakpoint-prefixed column span classes (e.g.
md:col-span-6) - Named grid areas that components are expected to fill by convention
- Utility classes for arbitrary widths or margins on layout regions
- Nested page layouts
- A fixed navigation rail or app-shell chrome component
If you need something from this list, open a catalog decision document first. Introducing ad-hoc layout utilities without a decision record leads to drift that is hard to reverse.
A digital services project by Flexion