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.
| property family | utilities | CSS property set |
|---|---|---|
| padding | p-* px-* py-* pt-* pr-* pb-* pl-* | padding / side-specific |
| margin | m-* mx-* my-* mt-* mr-* mb-* ml-* | margin / side-specific |
| gap | gap-* gap-x-* gap-y-* | gap / column-gap / row-gap |
| sizing | w-* h-* min-w-* min-h-* | width / height / min variants |
| position | top-* right-* bottom-* left-* inset-* | inset properties |
| space-between (removed) | 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.)
probe: p-4
padding: β
probe: gap-4
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 suffix | multiplier N | default px (0.25rem) | rem |
|---|---|---|---|
-0 | 0 | 0px | 0 |
-px | β | 1px | 1px (fixed) |
-0.5 | 0.5 | 2px | 0.125rem |
-1 | 1 | 4px | 0.25rem |
-1.5 | 1.5 | 6px | 0.375rem |
-2 | 2 | 8px | 0.5rem |
-3 | 3 | 12px | 0.75rem |
-4 | 4 | 16px | 1rem |
-5 | 5 | 20px | 1.25rem |
-6 | 6 | 24px | 1.5rem |
-8 | 8 | 32px | 2rem |
-10 | 10 | 40px | 2.5rem |
-12 | 12 | 48px | 3rem |
-16 | 16 | 64px | 4rem |
-24 | 24 | 96px | 6rem |
-13 (v4 dynamic) | 13 | 52px | 3.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
column-gap: β
grid Β· grid grid-cols-3 gap-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.
| utility | compiled CSS | rescales with --spacing? |
|---|---|---|
p-4 | padding: calc(var(--spacing) * 4) | β yes |
p-13 | padding: 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
| intent | pattern | why |
|---|---|---|
| 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 |