Skip to content

Spacing

A 4px grid, fifteen steps, and one job: make the relationship between two things visible before either of them is read.

Live preview

Everything below is the real component. Change the controls, tab through it, and turn on Inspector Mode to read any value off the screen.

The scale
space-0.52px
space-14px
space-1.56px
space-28px
space-2.510px
space-312px
space-416px
space-520px
space-624px
space-832px
space-1040px
space-1248px
space-1456px
space-1664px
space-2496px

Proximity

Both panels contain identical content. The left one uses two gap values; the right uses one. Only the left is scannable.

Correct — gaps encode grouping
Account
Email address
Password
Notifications
Product updates
Security alerts
Wrong — one uniform gap
Account
Email address
Password
Notifications
Product updates
Security alerts

Density

Three densities, one component. Compact is for power users on a large screen; relaxed is for touch and for first-time users.

Compact36px row
api-gateway
auth-service
billing-worker
Default44px row
api-gateway
auth-service
billing-worker
Relaxed56px row
api-gateway
auth-service
billing-worker

Optical vs mathematical

Equal padding on all four sides of a text block looks bottom-heavy, because descenders and line-height already add space below. Trim 1–2px off the bottom for optical balance.

Mathematically equal16px on all sides
Deploy to production
Optically equal16px top, 14px bottom
Deploy to production

Interaction states

Every state a user can put this component into, rendered side by side. If a state is missing here, it is missing in production too.

4pxAtomic nudge
8pxInline gap
12pxControl padding
16pxDefault gap
24pxSubsection
32pxBlock
48pxSection
64pxPage rhythm

Anatomy

Every part, every measurement, and the reason it is that number.

Production

3 regions · 12 instances

us-east-1
eu-west-2

One card, five distinct spacing decisions. The hatched region is the card’s own padding.

  1. Container padding20px

    Larger than any internal gap, so the content reads as sitting inside the card rather than being clipped by it. 20px is the sweet spot for a 16px-radius card.

  2. Title → subtitle4px

    The tightest gap in the card, because these two strings are one unit. Anything larger and the subtitle starts to read as separate metadata.

  3. Header → list16px

    4× the title gap. That ratio is what makes the header a header rather than the first list item.

  4. Between list rows8px

    Half the header gap. Rows are more related to each other than they are to the header, and the spacing says so.

  5. Footer separation20px + 1px rule + 16px

    The divider gets more space above than below because the eye reads the rule as belonging to the content beneath it.

Design tokens used

Values are read live from the running stylesheet, so this table can never drift from the code. Click any value to copy it.

Spacing

TokenValueUsed for
gap-1Label to required marker
gap-1.5Icon to label in small controls
gap-2Icon to label, chip rows, button rows
gap-3Input padding, table cells
gap-4Default gap between related elements
gap-5Card padding, stacked form fields
gap-6Subsections, dialog padding
gap-8Distinct content blocks
gap-12Page sections
gap-14Documentation section rhythm
px-6 / px-10Page gutters, mobile and desktop

Radius

TokenValueUsed for
--radius-xlCard corners — pairs with 20px padding

Recommended sizes

Pick a size from this table. Do not invent a new one — a fourth height is how a design system starts dying.

SizeLabel gapWhen to use
Inline (xs)4–6pxInside a single control: icon to label, label to badge.
Tight (sm)8pxBetween siblings that form one visual object — a row of chips or buttons.
Related (md)12–16pxBetween elements in the same group. The default.
Grouped (lg)20–24pxBetween groups inside one section. Also standard card padding.
Section (xl)32–48pxBetween distinct sections of a page.
Page (2xl)56–96pxBetween major page regions, and above the footer.

Do

Keep the group gap at least 2× the item gapThis single ratio is what makes grouping unambiguous. At 1.5× the eye hesitates; at 2× or more it does not have to think about it at all.
.list { display: flex; flex-direction: column; gap: 12px; }
Use gap, not margins on childrengap belongs to the container, so it cannot collapse, cannot leak past the last child, and cannot be accidentally doubled by a sibling’s margin. It also survives reordering.
Give the container more padding than its contents have gapIf a card has 16px padding and 16px internal gaps, the content looks like it is trying to escape. Padding one step above the largest internal gap reads as deliberate containment.
CompactDefaultRelaxed
Offer density as a user preference in data-heavy productsAn analyst on a 32-inch monitor and a manager on a laptop want different row heights. One toggle that swaps a padding token satisfies both without a second design.

Don't

Do not use one gap for everythingUniform spacing means no grouping information at all. The user has to read every label to work out the structure, which is exactly the work spacing exists to remove.
margin-top: -3px; /* looks right on my screen */
Do not fix alignment with a one-off negative marginA −3px nudge is a symptom: something upstream has the wrong padding or the wrong line-height. The nudge will break the moment the font size, the icon or the container changes.
Do not stack margin and gap on the same axisThe two add up and you get 28px where you designed 16px. Worse, it only shows up between certain siblings, so it reads as a random inconsistency.
Do not remove whitespace to fit more inCramming raises the perceived complexity of a screen far faster than it raises the amount of information on it. If everything must be visible at once, the answer is a different layout, not smaller gaps.

Accessibility

Not a checklist to run at the end. These are the requirements the component was built from.

1.4.12Text SpacingAA2.5.8Target Size (Minimum)AA1.4.10ReflowAA

Contrast

  • Whitespace is not subject to contrast rules, but it is the cheapest way to improve legibility for low-vision users — more space around a block raises comprehension more than raising contrast past AA does.
  • Where a divider would need to meet 3:1, extra spacing achieves the same separation with no contrast obligation at all.

Keyboard

TabSpacing does not affect focus order, but visual grouping must match DOM order or keyboard users get a different structure than sighted users.

Screen readers

  • Spacing is invisible to screen readers. Every group the eye sees must also exist as a list, a fieldset, a section, or a labelled region.
  • Do not use empty elements or non-breaking spaces to create space — they are announced and they pollute the reading experience.

Focus & touch

  • Focus rings sit at 2px offset, so any element needs at least 4px of clear space around it or the ring is clipped by a neighbour. This is a real constraint on tight layouts.
  • Adjacent touch targets need at least 8px of clear space between them, and 44 × 44 each. Two 36px buttons 4px apart is the most common target-size failure in real products.
AttributeApplied toNotes
Text spacing overrideUser stylesheetWCAG 1.4.12 requires the layout to survive line-height 1.5, paragraph spacing 2em, letter-spacing 0.12em and word-spacing 0.16em. Fixed-height containers are the usual failure.
role="group" + aria-labelledbyVisual groupsSpacing conveys grouping visually. Assistive tech needs the grouping expressed structurally as well — a fieldset, a list, or a labelled group.

Code

Example usage

tsx
1// Gap on the container, never margins on the children2<div className="flex flex-col gap-3">3  <Field label="Email" />4  <Field label="Password" />5</div>67// Group gap is 2x the item gap8<form className="flex flex-col gap-8">        {/* between groups  */}9  <fieldset className="flex flex-col gap-4">  {/* within a group  */}10    <Field label="First name" />11    <Field label="Last name" />12  </fieldset>13  <fieldset className="flex flex-col gap-4">14    <Field label="Company" />15    <Field label="Role" />16  </fieldset>17</form>1819// Container padding one step above the largest internal gap20<Card className="p-5">21  <div className="flex flex-col gap-4">…</div>22</Card>2324// Density as a token swap, not a second component25const pad = { compact: 'px-3 py-1.5', normal: 'px-3.5 py-2.5', relaxed: 'px-4 py-4' }26<td className={pad[density]}>{value}</td>

CSS

css
/* The scale is Tailwind's default 0.25rem base — every step is a
   multiple of 4px, so nothing can land off-grid by accident. */

.stack        { display: flex; flex-direction: column; gap: 1rem; }   /* 16 */
.stack--tight { gap: 0.5rem; }                                        /*  8 */
.stack--loose { gap: 2rem; }                                          /* 32 */

/* Optical padding: trim the bottom, because line-height already
   contributes space below the last baseline. */
.card {
  padding: 1.25rem 1.25rem 1.125rem;   /* 20 20 18 */
  border-radius: var(--radius-xl);
}

/* Page gutters scale with the viewport, content width does not */
.page {
  padding-inline: 1.5rem;              /* 24 on mobile  */
  margin-inline: auto;
  max-inline-size: 76rem;
}
@media (min-width: 640px) {
  .page { padding-inline: 2.5rem; }    /* 40 on desktop */
}

/* Survive the WCAG 1.4.12 text-spacing override: no fixed heights
   on anything that contains text. */
.callout { min-block-size: 3rem; block-size: auto; }

Notes

Professional tips

  • When a layout feels wrong but you cannot name why, measure the gaps. Nine times out of ten two things that belong together are further apart than two things that do not.
  • Squint at the screen. Whitespace should resolve into clean rectangular groups. If you see one undifferentiated grey field, the gaps are uniform and the structure is invisible.
  • Space is cheaper than borders. Before adding a divider, try doubling the gap — you usually get the same separation with less visual noise.
  • Vertical rhythm matters more than horizontal. Users scroll, so an irregular vertical cadence is felt on every screen; an irregular horizontal one is often never noticed.

Performance

  • gap on flex and grid is resolved during layout with no extra boxes. Spacer divs cost DOM nodes, layout time, and are announced by screen readers.
  • Avoid animating padding or margin — both trigger layout on every frame. Animate transform: translate() instead, which stays on the compositor.
  • In a virtualised list, put the gap in the item height calculation rather than as a CSS gap, or the virtualiser will mis-measure and rows will jitter during fast scroll.

Common mistakes

  • Using margin-bottom on list items, then fighting the extra space after the last one with :last-child. gap solves this by construction.
  • Setting a fixed height on a container with text inside. It fails WCAG 1.4.12 the moment a user increases line-height, and it fails localisation immediately.
  • Collapsing margins between adjacent block elements producing a gap that is neither value. Flex and grid containers do not collapse margins, which is another reason to prefer gap.
  • Treating padding as a way to hit a target size. Padding that only exists to reach 44px is invisible and inconsistent — use a pseudo-element overlay instead.

Real-world recommendations

  • Adopt the 2× rule as a review checklist item. It is objective, it takes five seconds to check, and it catches most layout problems before they ship.
  • For dense enterprise tables, ship compact as a preference but never as the default. New users need the relaxed version to learn the structure; power users will find the toggle.
  • When a stakeholder says a page "looks empty", resist filling it. The usual fix is a narrower measure and a clearer hierarchy — the same content, better organised.
  • Design the mobile gutters first. 16px is too tight on a modern phone and 32px wastes a third of a 360px screen; 24px is the value that survives contact with real content.