The rhythm a
system runs on.
Spacing is the invisible scaffolding that makes a hundred screens feel like the work of one designer. 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. Twelve values cover the whole product. The constraint is the point: when there are only twelve 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.
Arbitrary
Six near-identical values doing the same job. Maintenance nightmare.
4px scale
Six clearly-distinct steps. Every component knows which to use.
What the scale buys you
- 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 distinguishing 36 from 40 — the same five-step ratio at small sizes (4 → 8 → 12 → 16 → 20) becomes redundant at large. Move to a Fibonacci-ish rhythm (32 → 48 → 64 → 80 → 96) instead.
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.
Padding
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.
Gap
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.
Margin
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.
Heuristic
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.
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.
Pulled from real design-system audits. 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.
Continue
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.