Skip to content

The publication for web craftspeople Sunday, 13 September 2026

Design & UX

Fluid typography with clamp(): sizes that scale without breakpoints

The CSS clamp() function scales text continuously between two bounds, with no stacked media queries. The catch is using it without breaking zoom and accessibility — which takes a few concrete precautions.

Illustration : typographie fluide avec clamp()

For years, adapting a site’s typography to screen size meant piling up breakpoints: one heading size for mobile, another for tablet, a third for the large screen. The clamp() function flips the logic — text grows and shrinks continuously between a lower and an upper bound — but it introduces an accessibility trap that many builds discover too late.

The problem with fixed sizes and breakpoints

A fixed pixel size ignores the available width: a heading tuned for a desktop screen crushes the layout on mobile, while a heading designed for mobile looks timid on a wide monitor. The historical answer — @media breakpoints — works, but it produces abrupt jumps at each threshold and a stylesheet that swells as screen sizes diversify. Between two breakpoints the size stays frozen, while the width, for its part, varies constantly. On top of that comes a rarely-counted maintenance cost: every new device family — foldables, ultra-wides, small phones — demands one more breakpoint, and the slightest rework of the type scale forces you to reopen each media query one by one.

The stakes meet those of type scales treated as design tokens: a single, reusable value that describes an intention rather than a frozen number.

clamp(): lower bound, ideal value, upper bound

The function takes three arguments: a minimum value, an “ideal” value usually expressed with a share of the viewport width (vw), and a maximum value. The browser picks the ideal value as long as it stays between the two bounds, and falls back to the nearest bound beyond them.

h1 {
  /* jamais moins de 1.75rem, jamais plus de 3rem,
     valeur idéale qui suit la largeur de vue entre les deux */
  font-size: clamp(1.75rem, 1rem + 3vw, 3rem);
}

The middle term deserves attention. A lone vw would track the width with no legible floor; the fixed rem part guarantees a base that never disappears. The combination 1rem + 3vw therefore reads as “a one-rem base, plus a slope proportional to the screen”.

Building a consistent fluid scale

Rather than eyeballing each size, a scale rests on a constant ratio between levels. The table below shows a restrained scale, from body copy to the largest heading, with bounds that overlap from one level to the next to avoid inversions at intermediate widths.

RoleLower boundFluid termUpper bound
Body1rem0.95rem + 0.25vw1.125rem
Subheading1.25rem1rem + 1vw1.5rem
Section heading1.5rem1rem + 2vw2.25rem
Main heading1.75rem1rem + 3vw3rem

Expressed as CSS variables, the scale becomes a set of reusable tokens, exactly like a colour palette:

:root {
  --step-0: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
  --step-1: clamp(1.25rem, 1rem + 1vw, 1.5rem);
  --step-2: clamp(1.5rem, 1rem + 2vw, 2.25rem);
  --step-3: clamp(1.75rem, 1rem + 3vw, 3rem);
}

body { font-size: var(--step-0); }
h2   { font-size: var(--step-2); }
h1   { font-size: var(--step-3); }

Accessibility before the effect. A size expressed only in vw does not react to the browser’s font-size setting: someone who has enlarged their default text is simply ignored. That is a direct failure of WCAG success criterion 1.4.4 on resizing text. The rem part in the middle term is therefore not cosmetic: it is what lets zoom keep working.

The zoom trap: why mix rem and vw

The most misunderstood point of clamp() is how it behaves under zoom. Viewport units (vw) are relative to the window, not to the font setting: text sized in pure vw does not grow when the user increases the browser’s font size. By slipping a rem part into the ideal term, you reintroduce a component that does respond to zoom. The practical rule fits in one sentence: the middle term must always contain a rem part, never vw alone.

A text size that no longer reacts to browser zoom is not fluid, it is broken.

Beyond text: spacing and container units

The same principle extends to margins, gutters and vertical rhythm, which benefit from breathing with the screen rather than jumping by steps. A recent development refines the approach further: container-relative units (cqi, the container’s inline width) let a component size its text according to the space it actually occupies, not the whole window. Paired with container queries, they keep a single component legible whether it fills a narrow column or the full width.

.carte { container-type: inline-size; }

.carte h3 {
  /* la taille suit la largeur de la carte, pas celle de la fenêtre */
  font-size: clamp(1.125rem, 0.9rem + 1.5cqi, 1.5rem);
}

Verify the fluidity before shipping

A fluid scale is checked through three distinct gestures, too often merged into one. The first is resizing the window: it validates the vw slope and the absence of an ugly jump between bounds. The second is browser zoom (Ctrl and +), which must enlarge the whole page evenly. The third, the most neglected, is the browser’s default font-size setting pushed to 200%: that is what exposes a size locked in pure vw, sitting still where the rest of the text has grown.

One last precaution concerns very large screens. Without an upper bound, a vw term keeps growing indefinitely and ends up producing oversized headings beyond 2,500 pixels wide; the maximum value of clamp() is therefore no detail — it is the guardrail that caps the runaway. Conversely, the lower bound protects legibility on small screens, where a heading that is too small becomes as much of a problem as one that is too large.

Key takeaways

Fluid typography replaces dozens of media queries with a handful of readable clamp() functions, provided you respect its grammar: two bounds that protect legibility, a middle term that mixes a rem base and a vw slope. It is that rem part that preserves zoom and, with it, accessibility. Moving sizes into CSS variables finally turns the scale into a maintainable set of tokens, ready to extend to spacing and container units.

For a long time I shipped headings in pure vw, seduced by the fluidity, until the day an accessibility audit showed me that my headings stayed stone-still when the user pushed font size to 200%. Since then I never sign off on a single clamp() whose middle term lacks a rem, and I systematically test my mockups with browser zoom, not just with window resizing. Fluidity is worth nothing if it is paid for in legibility. — Simon Janvier

Further reading: the reference documentation for the clamp() function on MDN, with its edge cases and browser support.

Read next