What you'll learn
- Quick answer
- What breakpoint-based type sizing gets wrong
- How clamp() actually picks a value
- The exact pixel value at any viewport width
- Building a scale from two design points instead of guessing a vw number
- Why the min and max should be rem, not px or pure vw
- clamp() works on any length property
- Testing your scale and common mistakes
- FAQ
Quick Answer
clamp(MIN, PREFERRED, MAX) picks one CSS value that grows continuously with the viewport but never drops below MIN or exceeds MAX. For font-size, that replaces a stack of breakpoint-based media queries with a single line, and you can calculate the exact pixel value it produces at any viewport width with plain arithmetic — no guessing required.
What breakpoint-based type sizing gets wrong
The traditional way to make text responsive looks something like this: a base font-size, then a handful of @media (min-width: ...) overrides bumping it up at each breakpoint.
h1 {
font-size: 1.5rem;
}
@media (min-width: 640px) {
h1 { font-size: 2rem; }
}
@media (min-width: 1024px) {
h1 { font-size: 2.5rem; }
}This works, but it has two real costs. First, the text doesn't actually scale with the viewport — it jumps. At 639px the heading is 1.5rem; at 640px, one pixel wider, it's suddenly 2rem. Anyone resizing a window (or any device sitting near that boundary) sees a visible snap instead of smooth scaling. Second, this doesn't scale as a workflow: every text style in your design system needs its own set of breakpoint overrides, and every one of those breakpoints has to be maintained by hand as the design evolves.
clamp() replaces the whole stack with one declaration that scales continuously and has exactly three numbers to maintain instead of one per breakpoint.
How clamp() actually picks a value
clamp(MIN, PREFERRED, MAX) is formally defined as max(MIN, min(PREFERRED, MAX)). In plain terms: the browser evaluates PREFERRED continuously, but never lets the final result go below MIN or above MAX. The trick that makes this useful for responsive type is that PREFERRED is usually written using a viewport-relative unit like vw, so it's the only one of the three values that actually changes as the browser window resizes. MIN and MAX stay fixed as hard floors and ceilings.
h1 {
font-size: clamp(1.5rem, 4vw, 2.5rem);
}Below some viewport width, 4vw is smaller than 1.5rem, so the browser uses 1.5rem. Above some larger width, 4vw exceeds 2.5rem, so the browser caps it at 2.5rem. In between, the text scales smoothly with the viewport, pixel by pixel, with no breakpoint at all.
The exact pixel value at any viewport width
Rather than trust that intuition, here's the actual arithmetic for clamp(1rem, 4vw, 2rem), computed directly (assuming the default 16px root font-size, so 1rem = 16px and 2rem = 32px, and 4vw means 4% of the viewport width in pixels):
320px viewport -> 16.00px (1.000rem)
375px viewport -> 16.00px (1.000rem)
400px viewport -> 16.00px (1.000rem)
500px viewport -> 20.00px (1.250rem)
600px viewport -> 24.00px (1.500rem)
700px viewport -> 28.00px (1.750rem)
800px viewport -> 32.00px (2.000rem)
900px viewport -> 32.00px (2.000rem)
1200px viewport -> 32.00px (2.000rem)Two clean breakpoints fall out of the formula itself, and you can find them without a browser: the minimum kicks in wherever 4vw equals 16px, which is 16 / 0.04 = 400px. Below a 400px viewport, the value is pinned at exactly 1rem. The maximum kicks in wherever 4vw equals 32px, which is 32 / 0.04 = 800px. Above 800px, the value is pinned at exactly 2rem. Between 400px and 800px, the font size scales linearly — the table above confirms it: at 600px (the midpoint), the value is exactly 24px, the midpoint between 16px and 32px.
That's the general method for reading any clamp() declaration: divide each bound (in pixels) by the vw coefficient (as a decimal) to find the two viewport widths where the curve flattens out.
Building a scale from two design points instead of guessing a vw number
In practice you rarely start from a vw number — you start from a design spec that says something like "16px at a 320px viewport, scaling up to 24px at a 1280px viewport." Here's how to turn that into a real clamp() declaration, worked out with actual numbers instead of a rule of thumb.
First convert everything to rem (16px root): minimum viewport is 320 / 16 = 20rem, maximum viewport is 1280 / 16 = 80rem, minimum font is 1rem, maximum font is 1.5rem. The slope of the line between those two points is:
slope = (maxFontRem - minFontRem) / (maxViewportRem - minViewportRem)
slope = (1.5 - 1) / (80 - 20)
slope = 0.5 / 60
slope = 0.008333... rem per rem of viewportExpressed as a vw coefficient (multiply by 100), that's 0.8333vw. The y-intercept — the value the line would hit at a zero-width viewport — comes out to 0.8333rem. Put together, the full declaration is:
h1 {
font-size: clamp(1rem, 0.8333rem + 0.8333vw, 1.5rem);
}Verifying this against real viewport widths (unclamped preferred value, then clamped result):
320px -> preferred 16.000px, clamped 16.000px
375px -> preferred 16.458px, clamped 16.458px
600px -> preferred 18.333px, clamped 18.333px
768px -> preferred 19.733px, clamped 19.733px
1024px -> preferred 21.867px, clamped 21.867px
1280px -> preferred 24.000px, clamped 24.000px
1440px -> preferred 25.333px, clamped 24.000pxAt exactly 320px it hits 16px on the nose, at exactly 1280px it hits 24px on the nose, and past 1280px (at 1440px) the formula would have kept climbing to 25.3px but the 1.5rem ceiling caps it at 24px instead — exactly the behavior the two design points asked for.
Why the min and max should be rem, not px or pure vw
The unit choice for MIN and MAX isn't cosmetic — it affects accessibility directly. rem is relative to the root element's font-size, which is exactly the setting a browser's zoom/text-size preference changes. If a low-vision user has bumped their browser's default font size up, every rem-based value in your clamp() grows proportionally with it. If you'd used fixed px values for MIN and MAX instead, those bounds would stay frozen regardless of the user's accessibility settings, silently defeating the one browser feature built specifically to help them.
The same logic applies to the preferred value: a pure 4vw with no rem component doesn't respond to the user's font-size preference at all, since vw is only ever a fraction of the viewport's pixel width. That's why the two-point formula above lands on 0.8333rem + 0.8333vw rather than a bare vw number — the rem term is what keeps the whole expression responsive to accessibility zoom, not just window width.
clamp() works on any length property
Fluid typography is the most common use case, but clamp() accepts any valid length value, which makes it just as useful for spacing and layout:
.card {
padding: clamp(1rem, 3vw, 2.5rem);
gap: clamp(0.5rem, 2vw, 1.5rem);
}
.container {
max-width: clamp(20rem, 90vw, 75rem);
}
.button {
border-radius: clamp(4px, 1vw, 12px);
}The same reasoning from the typography sections applies directly: work out (or derive from two design points) the vw coefficient, and keep the bounds in a unit that respects the property's context — rem for anything text-adjacent, and plain px is often fine for something like a border-radius where accessibility zoom isn't really a factor.
Testing your scale and common mistakes
The most common real mistake is passing a MIN that's larger than MAX. Going back to the formal definition, clamp(MIN, VAL, MAX) = max(MIN, min(VAL, MAX)): if MIN is greater than MAX, then min(VAL, MAX) can never produce anything larger than MAX, which is already smaller than MIN — so max(MIN, anything-at-most-MAX) always resolves to MIN, no matter what VAL is. In practice this means your text renders at a fixed size and never scales at all, which usually gets misdiagnosed as "clamp() isn't working" when it's actually working exactly as specified against inverted bounds.
To sanity-check a scale before shipping it, don't rely on eyeballing a resized browser window — run the same kind of arithmetic shown in this article (a five-line script, or even a calculator) at your actual content breakpoints, then confirm the browser agrees using devtools' responsive mode with the ruler or computed-styles panel open on the element. clamp() itself has had full support in Chrome, Firefox, Safari, and Edge since 2020, so once your math is right, there's no browser-compatibility layer left to worry about.
