Stop Writing Media Queries for Font Size
A design system audit found five breakpoints for one heading. clamp() replaces all of them with one line that never jumps.

A teammate opened a PR titled "fix hero heading on small screens." The diff added a media query. Mine, reviewing it, found four more already in that file — one per breakpoint, added over eighteen months by four different people, each one patching the width the last person didn't think of:
Five rules to make one number — the font size of one heading — track the width of the screen it's on. And it still didn't work everywhere: resize the window to 850px and the heading is stuck at the 992px value, a little too big for the space it actually has. Every gap between breakpoints is a size nobody chose, it's just whatever the nearest rule left behind.
Here's the part that stings: none of this has been necessary since 2020.
clamp() takes three values — a minimum, a preferred value, and a maximum — and returns whichever one the situation calls for:
Read it as a sentence: never smaller than 1.5rem, never bigger than 3rem, and in between, scale with the viewport. The five media queries above collapse into that one line — and unlike them, it doesn't have gaps. clamp() recalculates the size continuously, every pixel the viewport moves, so there's no "850px value" that got left behind. It's a formula, not a lookup table.
The middle value is where the "preferred" size lives, and it's 1rem + 2vw — a fixed part plus a viewport-relative part — not just 4vw on its own. That's not decoration. It's the one part of this pattern worth getting right, because the shortcut version quietly breaks something.
The formula you'll see in half the blog posts about this is:
Simpler, and it scales exactly the same way visually. Try it in the playground below and you won't see a difference — until you go into your browser's settings and bump the default font size up, the way a low-vision reader routinely does. The 4vw-only heading doesn't move. Not a little — not at all.
vw is a percentage of the viewport's width. It has no relationship to the page's root font size, so a user's default-font-size preference — a real accessibility setting, not an edge case — has nothing to attach to. rem does have that relationship: it's relative to the root element's font size, which is exactly what that setting changes. Mix a rem term into the preferred value and the heading grows when the user's preference does. Drop the rem term and you've built a heading that's fluid for you, in your browser, at your settings, and static for the one person turning a knob specifically to make it bigger.
The fix costs nothing: 1rem + 2vw instead of 4vw. Same visual curve, but now it answers to two inputs instead of one.
Runs right in your browser — poke at it and watch the concept react live.
Once clamp() clicks for type, the same three-value shape solves the same problem everywhere else a design system hardcodes a size per breakpoint: section padding, a hero's min-height, a sidebar's width, the max-width that controls line length in a paragraph. Anywhere the honest answer to "what size should this be" is "it depends on the viewport, within limits" — that's a clamp(), not a set of @media rules:
The article line is worth a second look: mixing ch (a character-width unit) into the preferred value gives you a paragraph that keeps a readable line length as it grows, instead of one that just gets wider forever on an ultrawide monitor.
clamp() interpolates a value. It can't change layout — it won't swap a two-column grid to one column, hide a sidebar, or reorder flex items, because those are structural decisions, not points on a number line. If the real requirement is "this should look and behave differently past a width," that's still a media query's job, and no amount of clamp() cleverness replaces it. The two aren't competing; clamp() just took over the part of the job — continuous scaling — that media queries were only ever approximating with a staircase.
Five media queries, four contributors, and gaps between every breakpoint were solving a problem clamp(MIN, PREFERRED, MAX) has handled in one line since 2020 — as long as the preferred value mixes a rem with the vw, so it still answers to the one setting that matters most.
Open your own stylesheet and search for @media rules that only ever change a font-size or a padding. How many of them are a clamp() waiting to happen?
Think it clicked? Take the 8-question quiz →
Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.
Thanks for reading! Let's stay connected:
- ⭐ GitHub — follow me and star the projects: github.com/parsajiravand
- 💬 Discord — join the frontend best-practices community: discord.gg/d9KRhuAwQ
- 📸 Instagram — frontend best practices, daily: @bestpractice___
- 💼 LinkedIn — linkedin.com/in/parsa-jiravand
- ✉️ Email (work & contract inquiries): bestpractice2026@gmail.com
Keep reading
One post a day, in your inbox
Each one with a runnable playground and a quiz. No pitch, no digest, unsubscribe in one click.
0 comments
Sign in to join the discussion, like comments, and save articles for later.