Clamp-based Fluid Typography
Font sizes that grow smoothly with the screen between a minimum and a maximum — without breaking browser zoom.
/*
Fluid type scale: each step grows smoothly from a 360px viewport
to a 1280px viewport, then stops.
Formula: clamp(MIN, INTERCEPT + SLOPE, MAX)
*/
:root {
--step--1: clamp(0.8rem, 0.76rem + 0.16vw, 0.89rem);
--step-0: clamp(1rem, 0.9rem + 0.43vw, 1.25rem);
--step-1: clamp(1.25rem, 1.05rem + 0.87vw, 1.75rem);
--step-2: clamp(1.56rem, 1.22rem + 1.52vw, 2.44rem);
--step-3: clamp(1.95rem, 1.38rem + 2.55vw, 3.42rem);
}
body {
font-size: var(--step-0);
line-height: 1.6;
}
h1 { font-size: var(--step-3); line-height: 1.1; }
h2 { font-size: var(--step-2); line-height: 1.2; }
h3 { font-size: var(--step-1); line-height: 1.3; }
small { font-size: var(--step--1); }clamp(MIN, PREFERRED, MAX) returns the preferred value, but never less than MIN and never more than MAX. If the preferred value depends on the viewport width (vw), the font size grows with the screen and stops at both ends.
Where the numbers come from
Take --step-0: we want 16px at a 360px-wide screen and 20px at 1280px. That's a straight line:
- Slope = (20 − 16) ÷ (1280 − 360) = 0.00435 → as a viewport unit that's
0.43vw(1vw is 1% of the width). - Intercept = 16 − 0.00435 × 360 ≈ 14.4px ≈
0.9rem(assuming the default 16px root size). - Result:
clamp(1rem, 0.9rem + 0.43vw, 1.25rem). Check it: at 360px, 14.4 + 1.56 ≈ 16px; at 1280px, 14.4 + 5.6 = 20px.
Don't use vw alone
font-size: 3vw ignores the user's font-size setting and barely changes when they zoom, which can fail WCAG 1.4.4 (Resize Text). Mixing a rem part into the preferred value — and using rem for min/max — keeps zoom and user preferences working.
The other steps use the same method with a ratio of 1.25 between steps at the small end and about 1.4 at the large end, so headings grow faster than body text.
Usage
/* Use the steps anywhere, not just on headings */
.hero-title {
font-size: var(--step-3);
}
.card p {
font-size: var(--step--1);
}