Fluid typography: CSS clamp() sizes that scale and still respect zoom
Fluid typography with CSS clamp() lets a heading grow smoothly from a phone size to a desktop size without a stack of media queries. The catch is zoom: a size made only of viewport units ignores the reader's zoom setting. Mix a rem value into the preferred size, keep the range modest, and test at 200%.
From breakpoints to a continuous scale
Responsive sites first changed font sizes in steps: one size below a breakpoint, a bigger one above it. Designers then experimented with viewport units (vw), so a headline could be a fixed share of the screen width, and with calc() formulas that combined a base size and a viewport share. The clamp() function made the pattern tidy: one declaration with a minimum, a preferred value and a maximum.
font-size: clamp(2rem, 1.2rem + 3vw, 4.5rem) reads as: never smaller than 2rem, never larger than 4.5rem, and in between, 1.2rem plus 3% of the viewport width. The browser computes the middle value and clamps it to the bounds.
Working out the preferred value
Pick the size you want at a small viewport and at a large one, then draw a straight line between them. For a heading of 32px at a 360px screen and 72px at a 1280px screen:
- Slope: (72 − 32) ÷ (1280 − 360) = 0.0435, which is 4.35vw.
- Intercept: 32 − 0.0435 × 360 ≈ 16.3px, which is about 1.02rem at the default 16px root size.
- Declaration:
clamp(2rem, 1.02rem + 4.35vw, 4.5rem).
Online fluid-type calculators do the same arithmetic, and it is the whole trick of fluid typography: CSS clamp() wrapped around a straight line between two sizes. Whichever you use, keep the intercept in rem rather than px so it follows the reader's default font size, and round the numbers: nobody can see the difference between 4.35vw and 4.4vw.
Why zoom matters
WCAG 2.2 success criterion 1.4.4, Resize Text, asks that text can be resized up to 200% without assistive technology and without losing content or function. Browser zoom works by making CSS pixels larger, which also makes the viewport narrower in CSS pixels. A size of pure vw therefore stays the same physical size when the reader zooms: the viewport shrinks by exactly as much as the pixels grow.
| Declaration | On zoom | Verdict |
|---|---|---|
font-size: 5vw | Doesn't grow | Fails for text that needs resizing |
clamp(1rem, 5vw, 3rem) | Grows only once it hits a bound | Unreliable |
clamp(1rem, 0.8rem + 1vw, 1.5rem) | The rem part grows, the vw part doesn't | Usually fine if the range is modest |
font-size: 1.125rem | Grows fully, and follows the browser's default font size | Safe for body text |
The wider the gap between minimum and maximum, the more of the size comes from vw and the less zoom can do. A practical test beats any formula: open the page on a laptop-width window, zoom to 200%, and check the headings and body really got about twice as large and that nothing overflows. Reflow at 320 CSS pixels wide, criterion 1.4.10, is the companion test, covered in zoom and reflow.
What should and shouldn't be fluid
- Display and hero headings
- The main beneficiaries. The jump from a 40px phone headline to a 120px desktop one is too big for two or three breakpoints to handle gracefully.
- Section headings
- Fluid, with a narrow range, so they stay clearly below the hero.
- Body text
- Usually a fixed rem value, perhaps one step larger on wide screens. Fluid body text that shrinks on phones is a common way to end up below 16px.
- Labels, captions, buttons
- Fixed. They sit in components with fixed padding, and resizing them with the window looks odd.
- Spacing
- Section padding can use
clamp()in the same way, so whitespace and type scale together.
A fluid type scale is the same idea applied to every step: each heading level gets its own clamp, and the ratio between steps is larger on desktop than on mobile, because small screens can't afford dramatic jumps. See type scale for the web for choosing the ratios themselves.
Fluid type in Anticode
Anticode's design systems scale their headings between phone and desktop widths with clamp() and fixed minimum and maximum sizes, and set body text at a fixed size of at least 16px; the Critic checks that body size on every build. Section padding is fluid in the same way. The visual editor's headline scale changes how large the hero headline gets, in three steps, without touching the other levels.
Questions people ask
Does using vw for font size break zoom?
On its own, yes: text set only in viewport units doesn't get larger when the reader zooms. Combine it with rem inside clamp() and test at 200%.
Is clamp() supported everywhere?
All current major browsers support clamp(). Very old browsers ignore the declaration, so a plain font-size before it gives them a fallback.
Should I use clamp() or media queries?
clamp() for sizes that should change smoothly, media or container queries for layout changes, such as columns collapsing. They work well together.