Published October 17, 2026
Converting media queries to CSS clamp() is the highest-value refactor I know for an old stylesheet. This was the pattern everywhere a few years ago, and I have written it more times than I want to admit:
h1 { font-size: 24px; }
@media (min-width: 768px) { h1 { font-size: 36px; } }
@media (min-width: 1024px) { h1 { font-size: 48px; } }
And this is the replacement:
h1 { font-size: clamp(1.5rem, 4vw + 1rem, 3rem); }
Same endpoints, no jumps at breakpoints, no second-guessing what happens at 900px. But the conversion is not a find-and-replace job. Some media queries are doing work that clamp() cannot do, and the preferred-value formula has one pitfall that produces garbage if you skip it.
The rule: convert continuous values, keep discrete ones
clamp() owns anything that changes smoothly: font sizes, line heights, padding, gaps, container widths. A media query that bumps font-size up at 768px is asking for a continuous value with extra steps. Convert it.
Leave alone anything that changes state. The navigation that collapses into a hamburger menu at 720px. The card grid that switches from four columns to two. The sidebar that becomes a footer. Those are discrete decisions, and no function of viewport width can express them. The goal of the refactor is a stylesheet where media queries only make decisions and clamp() only scales things. Both get smaller.
The conversion formula, worked on padding
Take the sizes at your small and large breakpoints and interpolate linearly. If padding should be 16px at a 320px viewport and 64px at a 1200px viewport:
The slope is (64 - 16) / (1200 - 320) = 48 / 880 = 5.45vw. The offset is whatever makes the line hit 16px at 320px: 16 - (5.45 x 3.2) = -1.45px, about -0.09rem. So the declaration is:
padding: clamp(1rem, -0.09rem + 5.45vw, 4rem);
Yes, the rem offset is allowed to be negative. It just anchors the line. Check the math at both ends before you ship it: at 320px the preferred term computes to roughly 16px, at 1200px to roughly 64px. If it does not, the slope or the offset is wrong, and no amount of min/max fiddling fixes a bad preferred value.
The pitfall: bare vw without an offset
The most common conversion I see skips the rem offset and writes something like clamp(1rem, 5vw, 3rem). The problem is that a bare vw term scales from zero: on a 2000px ultrawide, 5vw is 100px, and the max does all the real work of holding the value down. The fluid middle ends up tiny or pinned, exactly the condition the crossover article on this site diagnoses. The rem offset is what makes the preferred value behave like a line through your two breakpoints instead of a line through the origin. Always compute it.
Two more conversions worth doing
Grid gaps breathe nicely as fluid values: gap: clamp(0.75rem, 0.5rem + 1vw, 2rem) keeps columns from drowning in whitespace on desktop while staying touchable on phones. Container width converts too, though min() is often the cleaner tool there: width: min(1200px, 100% - 2rem) gives you a capped container with consistent gutters in one declaration, no breakpoints at all.
Start with typography and spacing, the properties where stepping is most visible. Leave the layout skeleton on media queries, or better yet move component-level decisions to container queries. The stylesheet you end up with has fewer lines and, more importantly, fewer places where a value can be wrong at exactly one viewport width.
Frequently asked questions
Can clamp() replace all of my media queries?
No. clamp() replaces breakpoints that change a continuous value: font sizes, padding, gaps, widths. It cannot replace breakpoints that change a discrete state: navigation collapsing into a menu, a grid changing column counts, elements appearing or disappearing.
How do I calculate the preferred value when converting a media query to clamp()?
Use linear interpolation between your small and large breakpoints: slope = (large size minus small size) divided by (large viewport minus small viewport), expressed in vw, then add a rem offset so the preferred value hits both sizes exactly. Set the min and max to the sizes at each breakpoint.
Should the min and max in clamp() use rem or px?
Use rem for bounds on text so they scale with the reader's root font size and zoom settings. px bounds freeze at absolute sizes; a reader who bumps their default font size will not get larger clamped text if the max is in px.
Is clamp() safe to use without a media query fallback in 2026?
In all modern browsers, yes: clamp() has been supported since 2020. For projects that must serve very old browsers, a plain declaration before the clamp() line provides a static fallback at negligible cost.
Generate the clamp() line from your breakpoints.