Web development
clamp() for fluid typography: the formula and three mistakes
clamp() grows text between a minimum and a maximum. The correct formula, the three mistakes that make it unreadable and how to check it at any width.
Key points
- clamp() takes three values: minimum, preferred, maximum. The preferred one is what makes the scale fluid.
- The minimum goes in rem, never in px: the text must follow browser zoom.
- If the preferred value uses vw alone, zoom changes nothing: always add a rem term.
Short answer: clamp() grows text continuously between a minimum and a maximum. The formula that holds up is clamp(1rem, 0.8333rem + 0.83vw, 1.5rem): a rem term plus a vw part.
The formula in one line
clamp() takes three arguments, in the order minimum, preferred, maximum. The preferred value is a straight line: the rem term fixes the starting point, the vw term sets the slope. Between 16px at 320px and 24px at 1280px the slope works out at 0.83vw.
# coefficienti per una scala 16px -> 24px fra 320px e 1280px
node -e "const lo=16,hi=24,min=320,max=1280;const s=(hi-lo)/(max-min);console.log(((lo-s*min)/16).toFixed(4)+'rem + '+(s*100).toFixed(2)+'vw')"
# atteso: 0.8333rem + 0.83vw
The expected output is 0.8333rem + 0.83vw. If yours ends in a vw value with no rem, the text will not follow the zoom and the reader cannot enlarge it.
The code
:root {
--step-0: clamp(1rem, 0.8333rem + 0.83vw, 1.5rem);
--step-1: clamp(1.25rem, 1rem + 1.25vw, 2rem);
--step-2: clamp(1.5rem, 1.1rem + 2vw, 2.75rem);
}
h1 { font-size: var(--step-2); line-height: 1.1; }
h2 { font-size: var(--step-1); line-height: 1.2; }
p { font-size: var(--step-0); line-height: 1.6; }
Three mistakes that reach production
| Mistake | What happens | Fix |
|---|---|---|
font-size: 4vw with no clamp | Below 12px on a phone, huge on a 4K screen | Add a minimum and a maximum |
| Minimum in px | Browser zoom no longer scales the text | Minimum in rem |
| Line height fixed in px | Lines overlap when the text grows | Unitless line-height |
The limitation to know
clamp() controls the size, not the scale. Two nearby headings with different slopes converge halfway down the viewport and the hierarchy flattens. Build the steps on one scale and check the h1 to h2 ratio at every width.
The second limit sits in mobile browsers: below 16px iOS zooms the page when a field takes focus. Keep the body minimum at 16px, even when the design asks for smaller text.
How to check it
- Resize the window from 320 to 1440px: the text must change with no visible jump.
- Set the zoom to 200%: the text must grow, not stay put.
- Check the h1 to h2 ratio at the narrowest width.
- Switch web fonts off and retry: the scale must hold on the system font too.
Where to continue
In the catalogue: hero blocks, components and blocks. The guides: fluid typography in Tailwind and Flexbox and Grid for designers; the comparison: Tailwind against CSS Modules.
Frequently asked questions
Is clamp() supported everywhere?
Yes on browsers in use: Chrome 79, Firefox 75 and Safari 13.1 onwards. No polyfill is needed, because a static px fallback stays valid.
Why is vw alone not enough?
Because vw ignores browser zoom and has no limits: on a wide screen the text grows huge and on a phone it shrinks. clamp() adds the ceiling and the floor.
clamp() or breakpoints?
clamp() for the type scale, breakpoints for layout changes. A typography breakpoint is only needed when the content changes structure, not when only the size changes.
How do I compute the preferred value?
As a line between two points: slope in vw, intercept in rem. The command in the article prints both coefficients for the scale you give it.
What about container query units?
When the component lives in a narrow column, cqi instead of vw ties the scale to the container. The rest of the formula does not change.