Fluid Type and Space
Date: 2026-09-27
clamp(min, preferred, max), with a preferred value ofrem + vw, scales a size continuously between two viewport widths instead of jumping at breakpoints. Each size is a straight line through two points. Keep arempart in it, or text stops growing when the user zooms.
Fluid type and space means font sizes and spacing that interpolate linearly between a minimum at a small viewport and a maximum at a large one, using clamp(). When and where to use it — the top few steps of a scale, never body text — is covered in Type Scale. This note is the arithmetic, and what it does to spacing and zoom.
It’s a straight line
size
40px ┤ ●━━━━━━━━━ max (clamped)
│ ╱
│ ╱
│ ╱ preferred = intercept + slope × viewport
│ ╱
24px ┤━━━━━━━━━━━● min (clamped)
└──────────┬──────────────────┬─────────
320px 1280px viewport width
Working out the preferred value
Target: 24px at a 320px viewport, rising to 40px at 1280px.
slope = (max size − min size) / (max vw − min vw)
= (40 − 24) / (1280 − 320)
= 16 / 960
= 0.01667 → per 100px of viewport, +1.667px
→ as a vw value: 0.01667 × 100 = 1.667vw
intercept = min size − slope × min vw
= 24 − 0.01667 × 320
= 24 − 5.33
= 18.67px → ÷ 16 = 1.167rem
check at 1280: 18.67 + 0.01667 × 1280 = 18.67 + 21.33 = 40px ✓
:root {
--step-3: clamp(1.5rem, 1.167rem + 1.667vw, 2.5rem); /* 24px → 40px */
}
h2 { font-size: var(--step-3); }Nobody does this by hand more than twice. Generators produce the whole scale — Utopia is the widely used one — but knowing it’s a line through two points is what lets you sanity-check the output and debug a size that looks wrong.
Fluid space
The same mechanism applied to spacing tokens: section padding and gaps that grow with the viewport, so a layout doesn’t need a breakpoint just to add breathing room.
:root {
--space-s: clamp(1rem, 0.917rem + 0.417vw, 1.25rem); /* 16px → 20px */
--space-xl: clamp(2rem, 1.33rem + 3.33vw, 4rem); /* 32px → 64px */
}
.section { padding-block: var(--space-xl); }Small spaces barely need it; large ones benefit most. A 4px gap scaling to 5px is noise. Section padding scaling from 32px to 64px is the difference between cramped mobile and airy desktop — Spacing Systems.
Pair the steps so they move together (a heading’s size and the space above it), or the proportions drift between the two ends of the range.
The zoom problem
Browser zoom enlarges rem and px, but vw stays tied to the viewport — and zooming in makes the viewport narrower in CSS pixels, so the vw part shrinks.
font-size: 5vw zoom 100% → 200%: text barely changes
← fails the need to resize text to 200%
font-size: clamp(1.5rem, 1.167rem + 1.667vw, 2.5rem) on a 1280px screen
zoom 100% viewport 1280 CSS px 18.67 + 0.01667 × 1280 = 40.0 CSS px → 40px on screen
zoom 200% viewport 640 CSS px 18.67 + 0.01667 × 640 = 29.3 CSS px → 59px on screen
← 1.47× larger, not 2×
- Never use
vwalone for type. Always add aremterm - The steeper the slope, the less zoom helps. A gentle ratio between min and max keeps 200% zoom reachable [CHECK: the commonly cited rule is to keep max ≤ ~2.5× min for WCAG (Web Content Accessibility Guidelines) 1.4.4 resize-text compliance; verify against a current source before relying on it] — WCAG
- Test at 200% zoom at both ends of the range, not just at the default
Failure modes
- Unbounded
vw— no max, so headlines become absurd on a 2560px monitor - Fluid body text — a comfortable reading size doesn’t depend on screen width, and shrinking it on mobile is exactly wrong
- Min and max viewports that don’t match the design range. The line keeps changing below and above them, clamped only by the size limits
- Mixing fluid and stepped values for related things, so they move out of proportion in the middle of the range
Where it connects
- Type Scale — which steps should be fluid at all
- Custom Properties — one property per step is the usual form
- Responsive Design — one of the intrinsic techniques that replace breakpoints