Web devCSS

Fluid typography with CSS clamp(): the formula, the accessibility catch, and a worked example

SENULA COMPANY · 2026-09-19 · 5 min read

A magnifying glass inspecting alphabet blocks to demonstrate smooth text scaling.

Font sizes that snap at breakpoints

Responsive CSS typically relies on media queries to swap font sizes at fixed widths. You set one size for phones, another for tablets, a third for desktops. Between those breakpoints the text stays frozen even as the layout keeps stretching. A heading that looks balanced at 768px feels cramped at 1023px, then jumps abruptly the moment the next query kicks in.

Fluid typography removes the steps. The text scales continuously as the viewport changes, with a hard floor and ceiling so it never becomes unreadable on a small screen or absurdly large on an ultrawide monitor.

Same article on two screens — text should scale, not jump
Same article on two screens — text should scale, not jump · AI-generated image

How the clamp function works

CSS clamp() accepts three values in order: a minimum, a preferred (dynamic) value, and a maximum.

font-size: clamp(min, preferred, max);

The browser evaluates the preferred value continuously. When it falls below the minimum, the minimum wins. When it exceeds the maximum, the maximum wins. Between those two bounds, the font scales fluidly with the viewport. No JavaScript resize listeners, no extra media queries — one line of CSS handles it.

Why pure vw breaks text zoom

A common first attempt is to set the preferred value to something like 4vw. That works for scaling: four percent of the viewport width does shrink and grow with the window. The problem shows up when a user with low vision activates browser text zoom.

WCAG 1.4.4 requires that text remain resizable up to 200% without assistive technology. rem units respond to browser zoom because they derive from the root font size the browser controls. vw units do not — they are locked to the physical pixel width of the window. If the preferred value contains only vw, pressing Ctrl + has zero effect on the text. The solution is to combine rem and vw in the preferred value so the formula responds to both viewport width and user zoom settings.

Always include a rem component in your preferred value. A clamp() that uses only vw passes visual QA on your screen but fails accessibility testing the moment someone zooms.

The slope-intercept formula

Building an accessible clamp() means solving a linear equation: preferred = intercept (rem) + slope (vw). You need four inputs from your design spec.

StepFormulaWhat it does
Convert viewports to remviewport ÷ 16Makes the math zoom-safe
Slope (rem/rem)(maxFont − minFont) ÷ (maxVP − minVP)Growth rate per unit of viewport
Slope in vwslope × 100Converts to viewport-width percentage
InterceptminFont − slope × minVPStarting offset in rem

The browser default root font size is 16px. If your project overrides it, substitute that value where 16 appears.

Worked example 1 — landing-page hero heading

A designer specifies: the hero h1 should be 20px on a 360px phone and 40px on a 1200px desktop.

Convert fonts to rem: 20 ÷ 16 = 1.25rem, 40 ÷ 16 = 2.5rem.

Convert viewports to rem: 360 ÷ 16 = 22.5rem, 1200 ÷ 16 = 75rem.

Slope = (2.5 − 1.25) ÷ (75 − 22.5) = 1.25 ÷ 52.5 = 0.02381.

Slope in vw = 0.02381 × 100 = 2.381vw.

Intercept = 1.25 − (0.02381 × 22.5) = 1.25 − 0.5357 = 0.714rem.

h1 {
  font-size: clamp(1.25rem, 0.714rem + 2.381vw, 2.5rem);
}

Quick sanity check: at a 600px viewport (37.5rem), the preferred value is 0.714 + (0.02381 × 37.5) = 0.714 + 0.893 = 1.607rem ≈ 25.7px. That sits neatly between the 20px floor and the 40px ceiling.

Worked example 2 — documentation body text

Body copy on a docs site needs to stay comfortable everywhere. The spec calls for 16px (1rem) at 360px and 20px (1.25rem) at 1200px.

Slope = (1.25 − 1) ÷ (75 − 22.5) = 0.25 ÷ 52.5 = 0.004762.

Slope in vw = 0.476vw.

Intercept = 1 − (0.004762 × 22.5) = 1 − 0.1071 = 0.893rem.

p {
  font-size: clamp(1rem, 0.893rem + 0.476vw, 1.25rem);
}

The body text grows by less than one pixel for every 100px of extra viewport width — subtle enough to feel natural, not stretchy. If you would rather skip the arithmetic, enter the four values (min size, max size, min viewport, max viewport) into CSS clamp() Calculator to get the finished line with rem and vw, ready to copy.

Tool for this postCSS clamp() CalculatorFluid font-size code that scales with viewport widthNo upload · Free · No installOpen →
Planning font sizes on a wireframe before writing the CSS
Planning font sizes on a wireframe before writing the CSS · AI-generated image

Building a full type scale

Once you have the formula down, apply it to every text level in your design system. Here is a sample scale using the same 360–1200px viewport range.

ElementMin (px)Max (px)clamp()
h12040clamp(1.25rem, 0.714rem + 2.381vw, 2.5rem)
h21832clamp(1.125rem, 0.75rem + 1.667vw, 2rem)
p1620clamp(1rem, 0.893rem + 0.476vw, 1.25rem)
small1214clamp(0.75rem, 0.696rem + 0.238vw, 0.875rem)

Generating all four lines by hand takes a few minutes of calculator work. Running each set of inputs through CSS clamp() Calculator produces the same result instantly, with the copy button saving you from transcription typos.

Common mistakes

Forgetting to test zoom. After writing your clamp rules, open the page and press Ctrl + (or Cmd +) five times. If the text does not grow, you likely used pure vw somewhere.

Swapping min and max. If the minimum value is larger than the maximum, the browser ignores the preferred value entirely and always uses the minimum. The text will not scale at all.

Using px inside clamp. Fixed pixel values for the min or max still work, but they will not respond to user zoom. Stick to rem for all three positions whenever possible.

Testing browser zoom — text must still grow when the user zooms in
Testing browser zoom — text must still grow when the user zooms in · AI-generated image

FAQ

Can I use clamp() for spacing and layout, not just font sizes?

Yes. The same formula works for padding, margin, gap, and any property that accepts a length value. The accessibility concern about zoom applies mainly to text, but fluid spacing keeps layouts proportional.

What happens in browsers that do not support clamp()?

All modern browsers (Chrome 79+, Firefox 75+, Safari 13.1+, Edge 79+) support clamp(). For older browsers, set a static fallback on the line above: font-size: 1.25rem; followed by font-size: clamp(...). The old browser uses the first rule; the new browser overrides with the second.

Is there a minimum contrast or size I should not go below?

WCAG recommends body text no smaller than the equivalent of roughly 16px at default zoom. For footnotes or captions, 12px is a common practical floor, but always verify contrast ratios at the smallest size your scale reaches.

Try it in your browser nowCSS clamp() CalculatorFluid font-size code that scales with viewport widthNo upload · Free · No installOpen →