gap & the spacing scale

[check: …]
πŸ“– Pair this live Tailwind demo with the companion guide (.md) β€” this page is the rendered ground truth. β†— Builds on frontend/tailwind: design tokens (the --spacing token IS the single variable rescaled here).

ONE variable β†’ the entire spacing scale

In v3, every spacing step (p-1, p-2, …) was a hardcoded rem value in a lookup table. In v4, the whole scale collapses into a single multiplier: calc(var(--spacing) * N). Default --spacing: 0.25rem (4px), so p-4 = calc(0.25rem * 4) = 16px. Any integer works (p-13, p-17) β€” no theme extension needed.

@theme { --spacing: 0.25rem; /* the ONE variable. Change it β†’ everything rescales. */ } /* generated CSS: .p-4 { padding: calc(var(--spacing) * 4); } β†’ 16px .gap-6 { gap: calc(var(--spacing) * 6); } β†’ 24px .w-8 { width: calc(var(--spacing) * 8); } β†’ 32px */
property familyutilitiesCSS property set
paddingp-* px-* py-* pt-* pr-* pb-* pl-*padding / side-specific
marginm-* mx-* my-* mt-* mr-* mb-* ml-*margin / side-specific
gapgap-* gap-x-* gap-y-*gap / column-gap / row-gap
sizingw-* h-* min-w-* min-h-*width / height / min variants
positiontop-* right-* bottom-* left-* inset-*inset properties
space-between (removed)space-x-* space-y-*v4 removed β€” use gap-* on the parent instead

1 Β· drag --spacing β†’ every utility rescales live

The slider sets --spacing on :root at runtime. Because every utility references var(--spacing), the two probes below (p-4 padding and gap-4 column-gap) rescale instantly β€” no recompile, no class changes. (Click the readout to reset to the 0.25rem default.)

0.25rem (4px)

probe: p-4

padding box

padding: β€”

probe: gap-4

A
B
C

column-gap: β€”

2 Β· the spacing scale (visualized)

Each bar's width is a Tailwind w-* utility β€” so the bar IS calc(var(--spacing) * N) rendered. Drag the slider above and watch every bar grow/shrink in lockstep. The numeric readout is the live computed width.

utility suffixmultiplier Ndefault px (0.25rem)rem
-000px0
-pxβ€”1px1px (fixed)
-0.50.52px0.125rem
-114px0.25rem
-1.51.56px0.375rem
-228px0.5rem
-3312px0.75rem
-4416px1rem
-5520px1.25rem
-6624px1.5rem
-8832px2rem
-101040px2.5rem
-121248px3rem
-161664px4rem
-242496px6rem
-13 (v4 dynamic)1352px3.25rem

⚑ v4 dynamic scale: unlike v3's fixed lookup table, v4 generates calc(var(--spacing) * N) for any integer N. p-13, p-17, p-42 all work out of the box.

3 Β· gap-* : identical in flexbox and grid

gap-* maps to the CSS gap property (or column-gap / row-gap via gap-x-* / gap-y-*). It works in both flex and grid containers β€” unlike old margin-based spacing, gap doesn't collapse and doesn't leak outside the container.

flex Β· flex gap-6

1
2
3

column-gap: β€”

grid Β· grid grid-cols-3 gap-6

1
2
3
4
5
6

row-gap / col-gap: β€”

gap-x-* sets column-gap only; gap-y-* sets row-gap only. Useful when you want tight rows but loose columns (or vice versa). Drag the --spacing slider and these rescale too β€” gap-6 is just calc(var(--spacing) * 6).

4 Β· arbitrary spacing β€” the escape hatch

When the scale doesn't fit, use bracket notation. The value is emitted verbatim β€” it does not reference var(--spacing), so it won't rescale with the slider. This is the escape hatch, not the norm.

utilitycompiled CSSrescales with --spacing?
p-4padding: calc(var(--spacing) * 4)βœ… yes
p-13padding: calc(var(--spacing) * 13)βœ… yes (v4 dynamic)
p-[13px]padding: 13px❌ no (literal)
gap-[0.75rem]gap: 0.75rem❌ no (literal)
mx-[clamp(1rem,5vw,3rem)]margin-inline: clamp(...)❌ no (literal)
gap-[var(--my-gap)]gap: var(--my-gap)❌ no (your var, not --spacing)

Reach for arbitrary values only for genuine one-offs (a Figma token, a fluid clamp()). For values that should track the system density, prefer the integer scale (p-13) so they rescale with --spacing.

intent β†’ pattern

intentpatternwhy
space between flex/grid children flex gap-6 / grid gap-6 gap doesn't collapse or leak β€” always prefer over mr-* on children
different row vs column gaps gap-x-8 gap-y-2 maps to column-gap + row-gap independently
rescale the ENTIRE design system @theme { --spacing: 0.3rem; } one variable β†’ every p/m/gap/w/h/inset rescales
off-scale but on-system value p-13 v4 generates any integer multiplier β€” prefer this over p-[3.25rem]
non-scale spacing (true one-off) p-[13px] literal value; bypasses the multiplier, won't rescale
between children only (v3 habit) flex gap-4 (NOT space-x-4) v4 removed space-x-*; gap replaces it and works in grid too
scoped density for one subtree <div style="--spacing:.2rem"> children resolve var(--spacing) to the tighter value β€” instant density zone