Spacing & Layout
Spacing & Layout
Grid System
12-column grid on desktop, 4-column on mobile/tablet, with 24px gap.
.container max-width: 1128px (desktop), full width (mobile).container .row 12-column grid, 24px gap.col-1 to .col-12 span columns on desktop.col-1-sm to .col-4-sm override to 4-column mobile gridBreakpoints
| Mixin | Range |
|---|---|
@include mobile |
max-width: 540px |
@include tablet |
541px – 1024px |
@include mobiletablet |
max-width: 1024px |
@include tabletdesktop |
min-width: 541px |
@include desktop |
min-width: 1025px |
@include fullwidth_desktop |
min-width: 1400px |
Panel Spacing
- Desktop: 80px top/bottom padding
- Mobile/tablet: 48px top/bottom, 24px sides
- Internal gap between patterns: 24px
Token tiers
Sizes in the library come from named scales in utilities/_mixins.scss, one
per decision: space() for rhythm, radius() for corners, stroke() for
border and outline widths, control-height() for interactive rows,
icon-size() for glyphs. A token names a decision,
so two properties that share a number do not share a token: a 24px card
padding is space(6), a 24px pill corner is radius(l). Retuning one scale
must not move the others. An unknown token raises a Sass @error and fails
the build, so a new value has to be added to the scale deliberately.
Values that are not any of these — asset dimensions, the hero min-height, a
widget clearance — stay literals, as a named constant or with a one-line
off-scale: comment saying why, so a reader can tell a decision from debt.
On a property stylelint checks (spacing, type, border width and radius), the
exception is a stylelint-disable-next-line comment with the reason instead;
see docs/css-tooling.md.
Spacing
space(<step>) returns <step> × 4px ($space-unit, $space-steps). Whole
steps only; zero is written as 0 at the call site. Inside a custom property,
interpolate: --gap: #{space(4)}.
| Step | Value | Typical use |
|---|---|---|
space(1) |
4px | icon/label nudges |
space(2) |
8px | title to subtitle |
space(3) |
12px | panel header gap |
space(4) |
16px | subtitle to body |
space(5) |
20px | breadcrumb padding |
space(6) |
24px | grid gutter, card padding, pattern gap |
space(8) |
32px | |
space(9) |
36px | |
space(10) |
40px | panel header padding-bottom |
space(12) |
48px | panel padding, mobile |
space(14) |
56px | hero copy margin |
space(20) |
80px | panel padding, desktop |
.panel .container { padding: space(20) 0; }.panel__header { @include flexcol(space(3)); }Radius
radius(<token>) from $radii: s 4px (inputs, tooltips, menu items),
m 8px (cards, buttons, tables), l 24px, pill 50% (radios, dots,
avatars).
.card { border-radius: radius(m); }Stroke
stroke(<token>) from $strokes: hairline 1px (dividers, input borders),
thin 2px (buttons, the focus ring, the header rule).
.button { border-width: stroke(thin); }hr { border-top: stroke(hairline) solid var(--border-mono-medium); }Control heights
control-height(<token>) from $control-heights: s 40px (header top bar,
pagination, footer mobile rows), m 48px (large fields), l 64px (header nav
bar, footer buttons). Asset dimensions such as the footer logo stay literals.
.pagination button { height: control-height(s); }Icon sizes
.material-symbols-outlined glyph sizes come from $icon-sizes, applied with
icon-size(<token>): s 24px (the base size), m 28px, l 32px.
.material-symbols-outlined { font-size: icon-size(m); }Padding Utilities
| Class | Value |
|---|---|
.p-4 |
4px |
.p-8 |
8px |
.p-12 |
12px |
.p-16 |
16px |
.p-24 |
24px |
.p-32 |
32px |
.p-36 |
36px |
Flex Mixins
| Mixin | Description |
|---|---|
@include flexcol($gap) |
Flex column; $gap is a space() step, or omit it for no gap |
@include flexrow($gap) |
Flex row; $gap is a space() step, or omit it for no gap |