Skip to content
Foundations

Space · radius · borders

A 4px base with half-steps, radii that climb with the size of the thing they round, and a border system where selection widens the boundary.

Spacing

--gd-space-00
--gd-space-12pxthe gap inside a segmented track
--gd-space-24pxicon-to-icon in an approval stack
--gd-space-36pxlabel to its control
--gd-space-48pxchip padding, tight stacks
--gd-space-510pxrow gaps
--gd-space-612pxTHE default gap between siblings
--gd-space-714pxcard padding, grid gutters in a panel
--gd-space-816pxcard padding on a comfortable surface
--gd-space-918pxsection padding
--gd-space-1020pxpanel padding
--gd-space-1124pxTHE grid gutter, and the gap between cards
--gd-space-1226pxgenerous panel padding
--gd-space-1332pxbetween subsections
--gd-space-1440pxbetween sections in a document
--gd-space-1548px
--gd-space-1656pxbetween top-level sections on a long page
--gd-space-1772pxlanding-page section rhythm
--gd-space-px1pxa hairline gap between stacked rows

Radius

--gd-radius-xs4pxa checkbox, a swatch
--gd-radius-sm6pxa skeleton bar, a mono tag
--gd-radius-md8pxan icon tile, a small chip
--gd-radius-lg10pxbuttons, inputs, selects
--gd-radius-xl12pxlist rows and cards
--gd-radius-2xl14pxpanels and grouped cards
--gd-radius-3xl16pxdialogs and sheets
--gd-radius-pill999pxchips, badges, switches, avatars

Borders & selection

Rest
Selected
The padding shrinks by the same pixel the border grows, so the content does not move when a card is selected.
Code
<GdCard>Rest</GdCard>
<GdCard selected>Selected</GdCard>
--gd-border-width1pxevery default boundary
--gd-border-width-selected2pxa selected card — widen, never re-fill
--gd-border-focus-width2pxthe focus ring
--gd-border-focus-offset2pxthe gap between the control and its ring
--gd-border-focus-ring0 0 0 2px var(--gd-accent)the ring for a control packed against its neighbours — same accent and weight as base.css's outline, but flush instead of offset
--gd-border-focus-ring-invalid0 0 0 2px var(--gd-danger)the same ring on a control that is already in error — the hue changes, the geometry never does
--gd-border-rule-width3pxa callout's left rule, a tab's underline

Dividers

Draft Edited 2 minutes ago
Decorative by default, and therefore role=presentation. Pass semantic when the rule genuinely divides two sections a user needs told apart.
Code
<GdDivider />
<GdDivider strong />
<GdDivider vertical />
<GdDivider semantic />

The labelled rule

A label is the "or" between a form's primary path and its alternative. It is always announced, so semantic is moot once a label is present — and there is no vertical form.
Code
<GdButton variant="primary" block>Email me a code</GdButton>
<GdDivider label="or" />
<GdButton block>Continue with a council SSO</GdButton>

Rules

  • Take a gap from the ladder, never a raw pixel value. --gd-space-6 (12px) between siblings, --gd-space-11 (24px) between cards.
  • Match the radius to the size of the thing. A 16px radius on an 18px checkbox reads as a circle.
  • Widen the border to show selection; never re-fill it. --gd-border-width-selected is 2px.
  • Shrink the padding by the pixel the border gains. Without that compensation every selection nudges the layout.
  • Keep the radius constant between states. Only the border width changes.
  • Leave GdDivider decorative by default. Add semantic only when the rule genuinely divides two sections a user needs told apart.
  • Never hand-roll a labelled rule out of an <hr>. Use GdDivider's label.

Behavior & Anatomy

Why the radius steps with the thing

The radius steps with the size of the thing: a checkbox is 4, a button is 10, a list row is 12, a card is 14, a dialog is 16. A 16px radius on an 18px checkbox reads as a circle.

Why selection widens the border

A selected card WIDENS its border rather than changing its fill, so selection survives on any surface — a selected card on the sunken grey and one on white read identically, which a tinted fill cannot manage. That is also why the radius never changes between states, only the border width.

The padding shrinks by the same pixel the border grows, so the content does not move when a card is selected. Without that compensation every selection nudges the layout. A flush card has no padding to give back, so the pixel comes out of the border box instead.

Why the focus ring is stated once, as a shadow

base.css draws the default ring with outline, which cannot follow a custom radius; a control that owns its own ring — input, textarea, select, the hero search pill — sets box-shadow: var(--gd-border-focus-ring) instead and gets a ring identical in colour and weight that DOES follow its corners. Both are the solid accent at 2px, so a keyboard user sees one indicator everywhere. A soft alpha halo is decoration and cannot clear the 3:1 that WCAG 1.4.11 asks of a focus indicator, which is why focus is never expressed that way.

Why a divider is decorative by default

A screen reader announcing "separator" between every pair of rows in a settings panel is noise, so GdDivider is role="presentation" unless it is told otherwise. A labelled rule is the exception and is always announced — a rule with something to say is worth hearing — which is why semantic is moot once a label is present.

Navigate

Esc