Open a stylesheet from a few years ago and you will find this pattern: a base font size, then a media query at 768px bumping the headings, then another at 1024px, maybe one more at 1440px. Four declarations to express one idea, "this heading should be bigger on bigger screens," and the type still jumps awkwardly at every breakpoint instead of scaling smoothly between them. CSS clamp() replaces the whole stack with one line that scales continuously. Here is how it works, how to compute the values correctly, and where the sharp edges are.
What clamp() does
clamp(MIN, PREFERRED, MAX) resolves to the preferred value, bounded on both sides. The trick that makes it fluid: the preferred value mixes viewport units with fixed units, so it changes as the viewport changes, while the bounds keep it sane at the extremes.
h1 {
font-size: clamp(2rem, 1.2rem + 3.5vw, 3.5rem);
}
Reading it: never smaller than 2rem (32px), never larger than 3.5rem (56px), and in between, scale as 1.2rem + 3.5vw. At a 375px viewport that middle expression is 19.2px + 13.1px, which is below the floor, so the floor wins. At 1440px it is 19.2px + 50.4px, above the ceiling, so the ceiling wins. Between roughly 460px and 1190px, the size glides smoothly through every value from 32px to 56px. No breakpoints, no jumps.
The formula: stop guessing the middle value
Most clamp() tutorials wave at the preferred value as "something with vw in it, tweak until it looks right." You can do better. What you actually want is: size A at viewport width X, size B at viewport width Y, linear in between. That is a line through two points, and the slope-intercept math gives exact coefficients.
Say you want 18px body text at a 360px viewport and 21px at 1280px:
slope = (21 - 18) / (1280 - 360) = 0.00326
vw coefficient = slope * 100 = 0.326vw
intercept = 18 - (0.00326 * 360) = 16.83px = 1.052rem
body {
font-size: clamp(1.125rem, 1.052rem + 0.326vw, 1.3125rem);
}
That single declaration hits exactly 18px at 360px wide and exactly 21px at 1280px wide, with perfect linear interpolation between. The arithmetic is mechanical, which is why we built a CSS Clamp Calculator that takes your two sizes and two viewport widths and emits the finished declaration. Compute once, paste, done.
The accessibility rule you cannot skip
Here is the sharp edge that disqualifies half the clamp() examples on the internet: a preferred value built only from vw units does not respond to browser zoom or the user's font-size setting. font-size: 4vw is 4 percent of the viewport no matter how far the user zooms text, which fails WCAG 1.4.4 (text must be resizable to 200 percent).
The fix is exactly the formula above: always include a rem term in the preferred expression. 1.052rem + 0.326vw scales with user preferences through the rem component while staying fluid through the vw component. If you remember one thing from this article: never ship a clamp() whose middle value has no rem in it. Then verify by zooming to 200 percent and confirming the text actually grows.
Building a fluid type scale
One fluid size is nice; a system is better. Define your scale as custom properties and the whole site moves together:
:root {
--text-sm: clamp(0.875rem, 0.84rem + 0.15vw, 0.96rem);
--text-base: clamp(1.125rem, 1.05rem + 0.33vw, 1.31rem);
--text-lg: clamp(1.35rem, 1.22rem + 0.57vw, 1.67rem);
--text-xl: clamp(1.62rem, 1.41rem + 0.93vw, 2.14rem);
--text-2xl: clamp(1.94rem, 1.61rem + 1.47vw, 2.77rem);
--text-3xl: clamp(2.33rem, 1.81rem + 2.3vw, 3.55rem);
}
h1 { font-size: var(--text-3xl); }
h2 { font-size: var(--text-2xl); }
Notice the ratios widen as sizes grow: small text barely changes across viewports (a 14px caption does not need to become 22px on desktop), while display headings swing dramatically. A common approach uses a 1.2 ratio between steps on mobile and 1.28 on desktop, which falls naturally out of computing each step's min from the mobile scale and max from the desktop scale.
Beyond font-size: fluid spacing
The same math applies anywhere a length lives. Section padding that breathes with the viewport:
.section {
padding-block: clamp(3rem, 2rem + 4.5vw, 7rem);
}
.container {
padding-inline: clamp(1rem, 0.5rem + 2.5vw, 3rem);
}
Fluid spacing is arguably a bigger win than fluid type: spacing media queries outnumber font-size media queries in most real stylesheets, and they all collapse the same way. Gap, margin, border-radius on large cards, even icon sizes all take clamp() happily.
Edge cases and gotchas
- Container queries change the unit. If a component should size against its container rather than the viewport, use
cqiunits inside a container query context instead ofvw. Same formula, swap the unit and measure container widths instead of viewports. - vw includes the scrollbar on Windows. 100vw is the viewport including the scrollbar gutter in classic scrollbar configurations, so computed sizes can run a few pixels larger than your math at exact breakpoints. The error is under one percent for typography; do not chase it.
- Extreme aspect ratios. A 280px smartwatch-class viewport or a 3440px ultrawide will sit pinned at your min and max. That is the design working, not failing; choose bounds you would be happy to see frozen.
- Line height should not be clamped independently. Use a unitless
line-height: 1.5and let it scale with the fluid font size, or you will create combinations that never had a designer's eye on them. - Negative slopes work too. Nothing requires the value to grow with the viewport. A letter-spacing that tightens as headings get larger, or a horizontal padding that shrinks on wide screens to favor a centered column, is just a formula with size A larger than size B. The same two-point math produces a negative vw coefficient and clamp() handles it fine, though you swap the min and max bounds.
- Test the inheritance chain. A clamp() on
htmlchanges whatremmeans everywhere, including inside other clamp() expressions. Either clamp the root or the components, not both, unless you have done the compounding math deliberately.
Browser support and fallbacks
Support is no longer a concern in 2026: clamp() has shipped in every evergreen browser since 2020, and the last holdouts (pre-Chromium Edge, old Samsung Internet builds) are statistical noise for most audiences. If your analytics say otherwise, the fallback pattern is the usual two-declaration cascade:
h1 {
font-size: 2.5rem; /* fallback */
font-size: clamp(2rem, 1.2rem + 3.5vw, 3.5rem); /* modern */
}
Browsers that do not understand clamp() ignore the second line and keep the fixed size; everyone else gets fluid type. There is no need for @supports here because the override is harmless. The related functions min() and max() shipped on the same timeline and are worth knowing: width: min(100%, 65ch) is a one-line readable-measure container that replaces the classic max-width-plus-width pair.
When media queries are still right
clamp() interpolates a value; it does not change structure. Layout shifts, a sidebar collapsing, a nav becoming a hamburger, grid columns reflowing, still belong in media or container queries. The clean division: continuous properties get clamp(), discrete state changes get queries. Stylesheets organized this way end up with a handful of structural breakpoints and zero font-size breakpoints, which is the right shape.
Do the math once, with a tool
The linear interpolation formula is simple but tedious, and hand-tweaked vw coefficients are how accessibility bugs sneak in. Our free CSS Clamp Calculator takes your minimum size, maximum size, and the two viewport widths they should map to, and produces the exact rem-plus-vw declaration, accessible by construction because the rem term is part of the formula. Pair it with the Aspect Ratio Calculator for fluid media sizing, and if you are building out the rest of your visual system, the Gradient Generator and Box Shadow Generator cover the other fiddly-math corners of CSS.


