Layouts
Layout primitives are class-based so they work with static pages, CMS templates, and any component framework. Every primitive exposes its behavior through CSS custom properties — override per-instance to reshape without writing new selectors.
Page wrappers
cui-page centers content with horizontal gutters. Use cui-page-narrow for
long-form reading, cui-page-wide for dashboard-style content, and the default for everything
else. cui-page-header and cui-content handle title areas and constrained text.
Section heading
Page header pairs a title and short intro with consistent rhythm.
cui-content caps body width at --cui-content-size (68ch) so paragraph
measure stays comfortable.
| Token | Default | Effect |
|---|---|---|
--cui-container | 72rem | Default page width |
--cui-container-narrow | 48rem | Narrow page width |
--cui-container-wide | 96rem | Wide page width |
--cui-container-gutter | clamp(1rem, 4vi, 2rem) | Side padding |
--cui-content-size | 68ch | .cui-content width |
Stack & flow
cui-stack applies vertical rhythm with a single gap. cui-flow
does the same with margins so it can sit inside long-form HTML where direct children mix with text.
| Token | Default | Effect |
|---|---|---|
--cui-stack-gap | var(--cui-space-4) | Gap between stack children |
--cui-flow-space | var(--cui-space-4) | Margin between flow children |
Cluster
Inline group with wrap. Use for action rows, tag lists, breadcrumbs, and toolbar items. Override
--cui-cluster-justify to push items to opposite ends.
| Token | Default | Effect |
|---|---|---|
--cui-cluster-gap | var(--cui-space-3) | Gap between items |
--cui-cluster-align | center | Cross-axis alignment |
--cui-cluster-justify | flex-start | Main-axis alignment |
Grid
Auto-fitting responsive grid. Children use the available space until they hit
--cui-grid-min, at which point they wrap. No media queries required.
| Token | Default | Effect |
|---|---|---|
--cui-grid-min | 16rem | Minimum column width before wrap |
--cui-grid-gap | var(--cui-space-4) | Row and column gap |
Switcher
Sits on a single row until total inline-size drops below --cui-switcher-threshold, then
stacks. Useful for hero copy + media, label + control rows, or split sections that should collapse on
narrow viewports.
| Token | Default | Effect |
|---|---|---|
--cui-switcher-gap | var(--cui-space-4) | Gap between children |
--cui-switcher-threshold | 40rem | Width below which children stack |
Split
Two-column layout that collapses to one column under 48rem. Adjust the column ratio with
--cui-split-start and --cui-split-end.
| Token | Default | Effect |
|---|---|---|
--cui-split-gap | var(--cui-space-6) | Gap between columns |
--cui-split-start | 1fr | First column width |
--cui-split-end | 1fr | Second column width |
--cui-split-align | center | Block-axis alignment |
Center & cover
cui-center places its content in the middle of a region sized by
--cui-center-size. cui-cover reserves header / main / footer rows so the
main row fills remaining space — used for empty-states and viewport-tall splash screens.
| Token | Default | Effect |
|---|---|---|
--cui-center-size | 100vb | Center region height |
--cui-cover-size | 100vb | Cover region height |
Surface
Card-like wrapper with raised background, hairline border, soft elevation, and rounded corners.
Compose a layout primitive inside — cui-stack, cui-cluster, or
cui-split — rather than reaching for a typed card class. Every chrome property is an
override var, so a block can re-skin padding, radius, or shadow without redeclaring the rule.
For common steps, reach for the preset attributes — data-radius
(none/sm/md/lg), data-elevation
(flat/raised/floating), and data-padding
(none/sm/md/lg) — which set those same vars for
you. Inline vars still win, so the presets are a convenience, not a ceiling.
Default elevation, hairline border, raised background.
data-radius="lg" + data-elevation="floating" — no inline vars.
Flush, flat, square — override vars only, no new selector.
Composition models
Blocks reach for the primitives above in one of two ways. Which one a block uses is decided by what the block is, not by taste — pick by the heuristic below, not per element.
Model 1 — explicit composition (cards & repeated components)
For consumer-assembled, slotted blocks that appear many times — cards like
.cui-vacancy-card or .cui-event-card — keep the explicit primitive classes
and a slot class on each part. The slot classes document the slots a CMS author or template
fills, the markup is generated so the verbosity is paid once, and the card scale genuinely differs
from the primitive default.
Model 2 — contextual composition (sections & detail-page singletons)
For document-like blocks with a fixed structure that appear once per page —
.cui-page-intro, .cui-hero, a vacancy detail header — the block styles its
own descendants by element and position, the way .cui-prose styles raw
tags. The markup carries only the canonical primitive name (or nothing), and the per-element bespoke
names are deleted, not paired with a primitive. Cost: descendant selectors, but contained
inside the block — low specificity, no !important (the components layer still wins over
the primitives' own rules).
Heuristic. Repeats and is authored per item → Model 1 (explicit slot classes). Once-per-page structural slot → Model 2 (contextual descendants). When a card contains a mini-layout, the card body stays Model 1; only true section / detail layouts go Model 2.
Model 1 — explicit
Slot classes on each part. Verbose, but each slot is documented and the markup is generated.
Model 2 — contextual
Clean semantic markup; the block retunes its own descendants. Vocabulary stays the names you already know.
Shell
Sticky-header page chrome: header / body / footer in three rows. Use cui-shell-bar inside
the header for the toolbar row. The body fills remaining vertical space.
Body content
Only the body scrolls — the header and footer stay pinned. Try it.
| Modifier / token | Default | Effect |
|---|---|---|
data-fill | — | Fill parent height; body scrolls internally; header / footer stay pinned. |
--cui-shell-bar-size | 4rem | .cui-shell-bar minimum height |
App
Two-row, two-column application chrome: top bar plus a sidebar / main split. The sidebar is a
fixed-position drawer on narrow viewports and sits inline once the viewport is at least
48rem. Toggle with data-sidebar="open".
Operational dashboard
| Token | Default | Effect |
|---|---|---|
--cui-app-sidebar-size | 16rem | Sidebar width |
--cui-shell-bar-size | 4rem | Top bar minimum height |