Skip to content

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.

.cui-page · max 72rem
.cui-page-narrow · max 48rem
.cui-page-wide · max 96rem

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.

TokenDefaultEffect
--cui-container72remDefault page width
--cui-container-narrow48remNarrow page width
--cui-container-wide96remWide page width
--cui-container-gutterclamp(1rem, 4vi, 2rem)Side padding
--cui-content-size68ch.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.

First
Second
Third
TokenDefaultEffect
--cui-stack-gapvar(--cui-space-4)Gap between stack children
--cui-flow-spacevar(--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.

Item
Item
Item
Item
Item
TokenDefaultEffect
--cui-cluster-gapvar(--cui-space-3)Gap between items
--cui-cluster-aligncenterCross-axis alignment
--cui-cluster-justifyflex-startMain-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.

A
B
C
D
E
F
TokenDefaultEffect
--cui-grid-min16remMinimum column width before wrap
--cui-grid-gapvar(--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.

Primary
Secondary
TokenDefaultEffect
--cui-switcher-gapvar(--cui-space-4)Gap between children
--cui-switcher-threshold40remWidth 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.

Start (1fr)
End (1fr)
Start (1fr)
End (2fr)
TokenDefaultEffect
--cui-split-gapvar(--cui-space-6)Gap between columns
--cui-split-start1frFirst column width
--cui-split-end1frSecond column width
--cui-split-aligncenterBlock-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.

Focused content
Top
Centered main
Bottom
TokenDefaultEffect
--cui-center-size100vbCenter region height
--cui-cover-size100vbCover 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.

Surface card

Default elevation, hairline border, raised background.

With cluster
Confirm Cancel
Preset attributes

data-radius="lg" + data-elevation="floating" — no inline vars.

Re-skinned

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.

Workspace
Menu New

Body content

Only the body scrolls — the header and footer stay pinned. Try it.

Row 1
Row 2
Row 3
Row 4
Row 5
Row 6
Row 7
Row 8
Footer v1.0
Modifier / tokenDefaultEffect
data-fillFill parent height; body scrolls internally; header / footer stay pinned.
--cui-shell-bar-size4rem.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".

Workspace
Menu Create

Operational dashboard

Status
Activity
Queue
TokenDefaultEffect
--cui-app-sidebar-size16remSidebar width
--cui-shell-bar-size4remTop bar minimum height