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 rowsRadius
--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, avatarsBorders & selection
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 underlineDividers
Code
<GdDivider />
<GdDivider strong />
<GdDivider vertical />
<GdDivider semantic />The labelled rule
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-selectedis 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
GdDividerdecorative by default. Addsemanticonly when the rule genuinely divides two sections a user needs told apart. - Never hand-roll a labelled rule out of an
<hr>. UseGdDivider'slabel.
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.