09
Fluid Type
Your type scale looks right at 1440px. On a phone it doesn't move. clamp() fixes both ends without a breakpoint per heading.
6 min read
Your type scale works at the viewport you designed for. On a 390px phone, the hero heading is three lines and the font size hasn't moved. On a 2560px monitor, the same heading looks timid. You added a media query. Then another. Now you have four breakpoints per heading.
clamp() gives a type size a minimum, a maximum, and a formula to move between them. One declaration. No breakpoints.
Fluid Type
Readable Type
Drag from 320px to 1440px. The heading grows from 2rem to 3.16rem. The footer shows the computed size at each point. Below 320px it hits the minimum; above 1062px it hits the ceiling.
The Rule
Use clamp(min, preferred, max) for display sizes. Keep body text at a fixed 1rem.
The three arguments:
- min — smallest size, on the narrowest viewport. A hard floor.
- preferred — a viewport-relative expression. Usually
Xrem + Yvw. - max — largest size, on the widest viewport. A hard ceiling.
h1 {
/* Scales from 2rem at narrow to 3.16rem at wide */
font-size: clamp(2rem, 1.5rem + 2.5vw, 3.16rem);
}
h2 {
font-size: clamp(1.5rem, 1.2rem + 1.5vw, 2.37rem);
}
h3 {
font-size: clamp(1.2rem, 1.05rem + 0.75vw, 1.78rem);
}h1 {
/* Scales from 2rem at narrow to 3.16rem at wide */
font-size: clamp(2rem, 1.5rem + 2.5vw, 3.16rem);
}
h2 {
font-size: clamp(1.5rem, 1.2rem + 1.5vw, 2.37rem);
}
h3 {
font-size: clamp(1.2rem, 1.05rem + 0.75vw, 1.78rem);
}Fluid Scale
The heading
2.70rem · 43.2pxSection title
1.92rem · 30.7pxSubsection
1.41rem · 22.6pxBody text stays at 1rem regardless of viewport width.
1rem · 16px · fixedDrag from 320px to 1440px. The three headings scale at different rates — h1 is most aggressive, h3 subtlest. Body text holds at 1rem the entire time. That fixed row confirms the rule: fluid type is only for display sizes.
The vw in the preferred value is what makes the heading grow. At a narrow viewport, the vw term is small, and the sum approaches the minimum. At a wide viewport, the sum approaches the maximum. clamp() enforces both ends.
Choosing the values
The minimum and maximum should match your existing type scale. If your modular scale defines h1 at 2.37rem for body breakpoints and 3.16rem for wide screens, those become your min and max. Pick the vw coefficient so the crossover happens at the right viewport width.
/* To find the coefficient:
(max - min) / (max-viewport - min-viewport)
(3.16 - 2) / (1440 - 375) = 1.16 / 1065 ≈ 0.109
0.109 * 100 = 10.9vw — too aggressive for most cases.
Adjust down to match your target feel. 2-3vw is typical. *//* To find the coefficient:
(max - min) / (max-viewport - min-viewport)
(3.16 - 2) / (1440 - 375) = 1.16 / 1065 ≈ 0.109
0.109 * 100 = 10.9vw — too aggressive for most cases.
Adjust down to match your target feel. 2-3vw is typical. */There's no formula that works for every scale. Start from your existing breakpoint sizes and tune the coefficient until the transition feels smooth at 600–900px, where it spends the most time.
What not to make fluid
Body text shouldn't be fluid. 1rem translates to roughly 16px on most browsers, which has already been tuned for readability over decades. Making body text smaller on narrow viewports pushes it below readable minimums. Fluid type is for display sizes.
Common Mistake
Applying clamp() to body or paragraph font sizes. Body text below 1rem (16px) is too small on mobile for extended reading and fails WCAG 1.4.4 on some displays.
font-size: clamp() is supported in every major browser since 2021. There's no progressive enhancement concern.