Skip to content

Layout reference

Each pattern is a class that declares intent. Its data attributes choose a configuration, and the values come from tokens. An attribute value that is not listed here does nothing.

Stack - Vertical rhythm.

.stack { display: flex; flex-direction: column; gap: var(--stack-gap, var(--space-4)); }
Attribute Values Sets
data-gap 0 1 2 3 4 5 6 7 8 10 12 tight normal loose --stack-gap
data-align start center end stretch align-items
data-justify start center end between justify-content

Cluster - Horizontal grouping with wrapping.

.cluster { display: flex; flex-wrap: wrap; gap: var(--cluster-gap, var(--space-3)); align-items: var(--cluster-align, center); }
Attribute Values Sets
data-gap 0 1 2 3 4 5 6 8 tight normal loose --cluster-gap
data-align start center end baseline stretch --cluster-align
data-justify start center end between around justify-content

Grid - Responsive cards with minimum width.

.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(var(--grid-min, 280px), 100%), 1fr)); gap: var(--grid-gap, var(--space-5)); }
Attribute Values Sets
data-min 200 240 280 320 360 400 --grid-min
data-gap 2 3 4 5 6 8 10 --grid-gap
data-align start center end stretch align-items

Sidebar - Fixed sidebar + flexible main.

.sidebar { display: flex; flex-wrap: wrap; gap: var(--sidebar-gap, var(--space-6)); }
.sidebar > :first-child { flex-basis: var(--sidebar-width, 16rem); flex-grow: 0; flex-shrink: 0; }
.sidebar > :last-child { flex-basis: 0; flex-grow: 999; min-inline-size: 50%; }
Attribute Values Sets
data-side end >
data-sidebar-width 12 14 16 18 20 24 --sidebar-width
data-gap 4 5 6 8 10 --sidebar-gap

Center - Constrain width, keep things readable.

.center { box-sizing: content-box; margin-inline: auto; max-inline-size: var(--center-max, 70ch); padding-inline: var(--center-padding, var(--space-6)); }
Attribute Values Sets
data-max 50 60 70 80 90 40rem 48rem 56rem 64rem 80rem --center-max
data-padding 4 6 8 0 --center-padding
data-text center text-align

Switcher - side by side while every part has room, stacked when not. The threshold is the pattern’s own inline size at which the parts stop fitting; no media or container query is involved, so it responds to whatever space it is given, not to the viewport..

.switcher { display: flex; flex-wrap: wrap; gap: var(--switcher-gap, var(--space-6)); align-items: var(--switcher-align, stretch); }
.switcher > * { flex-grow: 1; flex-basis: calc((var(--switcher-threshold, 40rem) - 100%) * 999); min-inline-size: 0; }
Attribute Values Sets
data-threshold 30 36 40 44 48 52 56 --switcher-threshold
data-ratio 4:3 3:2 2:1 3:4 2:3 1:2 >
data-gap 4 6 8 12 loose --switcher-gap
data-align start center end --switcher-align