02 / System mechanics
The rhythm a system runs on.
Spacing is the scaffolding nobody sees. Get the scale right and components compose without negotiation. Get it wrong and every layout becomes a fresh argument about half a pixel.
01 / Why a scale
Without a scale, every value is a fresh decision.
A spacing scale is a closed set of values — usually multiples of 4 — that every component is allowed to use. Thirteen values cover the whole product. The constraint is the point: when there are only thirteen choices, no one wastes time on the difference between 13px and 14px, and every component lines up because they all snap to the same grid.
Most modern systems base the scale on 4px because it’s small enough to handle tight UI (icon padding, dense tables) and divides cleanly into 8, 16, 24, 32 — the values designers reach for anyway. An 8px base is too coarse for fine UI; a 2px base produces too many near-identical options.
Six near-identical values doing the same job. Maintenance nightmare.
Six clearly-distinct steps. Every component knows which to use.
- Visual rhythm. Multiples of 4 create predictable vertical and horizontal beats. The page reads as a composition, not a collage.
- Cheap review. “Snap to the scale” is a one-line code review comment that closes 80% of spacing debates.
- Refactor without redesign. Change one token and every component re-spaces consistently. No more grep for 13px.
02 / The scale
Thirteen values, one rule: snap to the scale.
A canonical 4px-based scale used in production by Tailwind, GitHub Primer, Atlassian, and most modern systems. The values get progressively further apart as you go up — ratio matters more than absolute distance at larger sizes.
Why the gaps widen. Past 32px the eye stops telling 36 from 40 apart, so the 4px steps that earn their keep at small sizes just add noise at large ones. The jumps double as the scale climbs — 8px through the middle (24 → 32 → 40 → 48), then 16px at the top (48 → 64 → 80 → 96).
03 / Padding · gap · margin
The three spacing tools that aren’t interchangeable.
They feel the same in Figma. They aren’t the same in code. Picking the right one is the difference between a component that composes anywhere and one that needs a special-case wrapper.
Inside an element
The cushion between an element’s border and its content. The component owns it.
padding: var(--space-4)
Use for: Button height, card breathing room, input fields. Anything the component should always have, regardless of where it’s placed.
Between siblings
Space between children of a flex or grid container. The parent owns it.
display: flex; gap: var(--space-4)
Use for: Rows of buttons, lists of cards, form fields. Modern default — no margin-collapse bugs, no last-child overrides.
Around itself (rare)
Space an element pushes against its surroundings. Collapses, leaks, hard to reset.
margin-top: var(--space-4)
Use for: Long-form prose where the parent can’t set gap. Otherwise prefer gap or padding — margin is the source of most spacing bugs.
Components own padding. Layouts own gap. Margin is a last resort, mostly inside prose (Markdown, blog posts) where the parent has no idea what’s coming next.
04 / Density
One scale, three densities.
The scale stays the same — what changes is which steps the components reach for. Dashboards need to fit more on screen; marketing sites need to breathe. A density mode multiplier lets the same component work in both contexts.
Compact
×0.75Data tables, dashboards, financial UIs — context where users scan dense rows.
Comfortable
×1.0Default web app surfaces, settings pages, content editing.
Spacious
×1.25Marketing sites, onboarding, anything tapped by thumbs on mobile.
row padding 6px · gap 8px
row padding 12px · gap 12px
row padding 18px · gap 16px
One component, one data set — only the spacing multiplier changes. Compact fits three more rows on screen; spacious gives thumbs room to tap. The scale never moves; the steps the row reaches for do.
05 / Responsive
The scale shrinks at small widths.
Section gaps that read as elegant at 1440px feel like dead air at 375px. Reduce macro-spacing (page sections, hero padding) on mobile; leave micro-spacing (inside components) alone — a button still needs the same cushion.
Notice the bottom two rows. Component gap and inline gap don’t change across breakpoints — they belong to the component, not the page. Only page-level spacing scales with viewport.
06 / Anti-patterns
Three things every team does first, and shouldn’t.
None of these are obscure — they’re the first three problems every system finds when it runs a token sweep.
Arbitrary values
Avoid
padding: 13pxPrefer
padding: var(--space-3)Once one component uses 13px, the next uses 14px, and the system has no rhythm. Snap every value to the scale or extend the scale — never invent in place.
Mixing padding and margin
Avoid
Card has margin-bottom: 24pxPrefer
Card sits inside a flex/grid with gap: 24pxMargin collapses, fights with siblings, and breaks composition. Modern layouts (flex + grid) let the parent own the spacing — children stay margin-free and reusable.
Spacing as decoration
Avoid
Add 80px between every section because it looks emptyPrefer
80px when sections are conceptually unrelated; 48px when they're a seriesWhitespace communicates relationships. Equal spacing between everything tells the eye nothing — vary the rhythm to signal hierarchy.
Spacing is one scale. layout is what you do with it.
Once the spacing tokens are locked, layout decides how they compose at the page level — grids, breakpoints, container widths, and the responsive patterns every product surface repeats.