How to make font size responsive — and why font-size in vw breaks zoom
You want a heading that's big on a laptop and doesn't spill off a phone screen. The shortest path any forum will suggest is font-size: 4vw. Four percent of the window width. It grows with the screen — lovely.
Check two things. On a 360-pixel-wide phone that's 14 pixels — the heading is smaller than the body text. On a 2560 monitor it's already 102 pixels.
Now press Ctrl and plus. Zoom 200%. The text didn't grow a single pixel. Someone who needs larger text can do nothing about it.
Why? When you zoom, the browser makes the window "narrower" in CSS pixels. So 4vw shrinks by exactly as much as the zoom enlarged everything. One cancels the other.
Below is how to do it properly in five steps.
Step by step: responsive font size with clamp()
-
Pick two points. What size you want on the narrowest screen and on a wide one. Say, body text: 16 pixels on a 360 phone and 24 pixels on a 1280 laptop. A heading: 28 and 48.
-
Work out how fast the font grows. Divide the size increase by the width increase. For body text: (24 − 16) / (1280 − 360) = 8 / 920 ≈ 0.0087. Multiply by 100 to get the
vwnumber: 0.87vw. -
Work out the starting offset. Take the small size and subtract the growth at the narrow width: 16 − 0.0087 × 360 ≈ 12.87 pixels. Convert to
remby dividing by 16: 0.804rem. Whyremand not pixels is covered in em vs rem:remrespects the user's browser font setting. -
Assemble clamp(). It takes three values: minimum, fluid middle, maximum.
body { font-size: clamp(1rem, 0.804rem + 0.87vw, 1.5rem); } h1 { font-size: clamp(1.75rem, 1.261rem + 2.174vw, 3rem); }The browser uses the middle but won't let it drop below the minimum or rise above the maximum. At 1280 pixels the body middle gives exactly 24 — it checks out.
-
Test with zoom. Open the page, press Ctrl and plus up to 200%. The text should visibly grow. It grows roughly one and a half times, not double — which is fine, but only under one condition from the next section.
Why the rem part matters
The whole trick is the rem + vw sum. The vw part shrinks when you zoom. The rem part grows. So overall the text still gets bigger.
The bigger the vw share, the weaker the growth on zoom. Hence a simple rule: the maximum should be no more than two and a half times the minimum. Browsers allow zoom up to 500%, and at that ratio the text is guaranteed to be enlargeable to double — which is what the WCAG accessibility standard requires.
Ours: 24 / 16 = 1.5 for body text and 48 / 28 ≈ 1.7 for the heading. There's headroom. But clamp(1rem, 5vw, 4rem) is 4 to 1, and a user with poor eyesight will hit the ceiling.
What not to do
- Don't write bare vw.
font-size: 4vwis always broken zoom. Even insidecalc()if there's noremin it. - Don't drop input text below 16 pixels. Otherwise Safari on iPhone zooms the page by itself when you tap the field. It's covered in why your input zooms in on iPhone. For
input, set a minimum of1rem. - Don't make everything fluid. Headings and large text need it. Small captions can stay fixed — the difference between 12 and 13 pixels saves nobody.
A handier way to keep the scale
To avoid copying formulas all over your CSS, put them in variables:
:root {
--step-0: clamp(1rem, 0.804rem + 0.87vw, 1.5rem);
--step-3: clamp(1.75rem, 1.261rem + 2.174vw, 3rem);
}
h1 { font-size: var(--step-3); }
How such variables work is covered in the article on CSS variables. In Tailwind you write the same thing right in a class: text-[clamp(1.75rem,1.261rem+2.174vw,3rem)].
What you get
One set of rules instead of a pile of @media blocks with different sizes. The font grows smoothly from phone to monitor, doesn't shrink to nothing on a narrow screen or balloon on a huge one. And most importantly, it honestly grows when the user zooms.
By default a neural net writes 4vw or five media queries. Ask precisely:
make the font responsiveBuild a responsive font scale with clamp(). Body text 16px at 360px width and 24px at 1280px, h1 28px and 48px, h2 22px and 34px. Minimum and maximum in rem, the middle as rem + vw, calculated from the two points. Maximum no more than 2.5x the minimum. Put them in CSS variables --step-0..--step-3. No bare vw and no @media for font sizes.Do all browsers support clamp()?
Yes. clamp() has worked in all current browsers since 2020, including Safari on iPhone. You usually don't need a fallback for old ones today.
Can you make responsive fonts with media queries instead?
You can, but the size will jump in steps at each breakpoint. clamp() gives smooth growth in one line and doesn't make you remember where each breakpoint is.
Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.


