Fluid Typography: A Practical Guide
Fluid typography makes font sizes scale smoothly between a minimum and maximum as the viewport grows, using CSS clamp() with a rem + vw middle value. For example, clamp(2rem, 1.5rem + 2.5vw, 3.5rem) grows a heading from 32px at 320px wide to 56px at 1280px wide.
What fluid type solves
The traditional approach to responsive text is a set of fixed sizes that switch at breakpoints: 32px on phones, 44px on tablets, 56px on desktops. It works, but the jumps are abrupt, and a heading that looks right at 768px may feel cramped at 767px.
Fluid typography replaces those steps with a straight line. Between two viewport widths, the font size grows proportionally; below the smaller width it stops at a minimum, and above the larger width it stops at a maximum. You write it once with clamp(), and every screen width in between gets an appropriate size.
How clamp() works
clamp(MIN, PREFERRED, MAX) returns the preferred value, but never less than MIN or more than MAX. It is supported in all current major browsers (see MDN's clamp() reference).
For fluid type, the preferred value is a line: a fixed part in rem plus a part that grows with the viewport in vw. The maths is the equation of a straight line through two points: (min viewport, min size) and (max viewport, max size).
slope = (maxSize − minSize) ÷ (maxViewport − minViewport)
intercept = minSize − slope × minViewport
font-size: clamp(minSize, intercept + slope × 100vw, maxSize);Work in pixels, then convert the intercept and bounds to rem (divide by 16) and the slope to vw (multiply by 100).
Worked example: a 32px to 56px heading
Goal: a heading that's 32px on a 320px screen and 56px on a 1280px screen.
- Slope: (56 − 32) ÷ (1280 − 320) = 24 ÷ 960 = 0.025
- vw value: 0.025 × 100 = 2.5vw
- Intercept: 32 − 0.025 × 320 = 32 − 8 = 24px = 1.5rem
- Bounds: 32px = 2rem, 56px = 3.5rem
font-size: clamp(2rem, 1.5rem + 2.5vw, 3.5rem);
Check it at 800px wide: 24 + 0.025 × 800 = 44px, halfway between, exactly as expected. The CSS clamp() generator does all of this for you, and the px to vw converter and px to rem converter help when you're checking values by hand.
Worked example: body text, 16px to 20px
Same viewports, smaller range: slope = 4 ÷ 960 ≈ 0.004167, so 0.4167vw. Intercept = 16 − 0.004167 × 320 ≈ 14.67px ≈ 0.9167rem.
font-size: clamp(1rem, 0.9167rem + 0.4167vw, 1.25rem);
Building a fluid type scale
Rather than computing each heading separately, define a set of steps as custom properties and reuse them.
:root {
--step-0: clamp(1rem, 0.9167rem + 0.4167vw, 1.25rem); /* 16 → 20px */
--step-1: clamp(1.25rem, 1.0833rem + 0.8333vw, 1.75rem); /* 20 → 28px */
--step-2: clamp(1.5rem, 1.1667rem + 1.6667vw, 2.5rem); /* 24 → 40px */
--step-3: clamp(2rem, 1.5rem + 2.5vw, 3.5rem); /* 32 → 56px */
}
body { font-size: var(--step-0); }
h3 { font-size: var(--step-1); }
h2 { font-size: var(--step-2); }
h1 { font-size: var(--step-3); }Notice that larger steps grow more than smaller ones. That's deliberate: headings need more contrast with body text on wide screens than on phones, where space is tight.
Choosing the min and max viewport
The viewport range decides how steep the line is. A few guidelines:
- Minimum viewport: 320px to 375px covers the smallest common phones. Below it, the minimum size applies.
- Maximum viewport: match the width where your content container stops growing, often 1200px to 1440px. Scaling text beyond the container's maximum width usually just makes lines shorter.
- Use the same range for every step so the whole scale changes together.
- Avoid very narrow ranges. Going from 16px to 48px between 400px and 600px creates a steep slope where small window changes produce big jumps.
Accessibility: keep rem in the formula
WCAG 2.2 Success Criterion 1.4.4 asks that text can be resized to 200% without assistive technology. Fluid type can break this in two ways.
- vw doesn't zoom. When you zoom a desktop browser, the viewport gets narrower in CSS pixels, so the vw part of the formula shrinks. Only the rem part grows. A pure
5vwheading can stay almost the same size at 200% zoom. - px ignores font settings. If the bounds are in px, a user who raises their default font size gets nothing at the extremes.
The fix is to use rem for both bounds and for the intercept, as in the examples above. A useful rule of thumb from accessibility testing is to keep the maximum size no more than about 2.5 times the minimum; beyond that, there are viewport widths where 200% zoom can't double the text. Results vary by browser, so test with zoom at 200% and with a larger default font size.
W3C's Understanding SC 1.4.4 explains the requirement in more detail. For background on the units themselves, see our guide to px, rem, em and vw.
Common mistakes
/* 1. vw alone: ignores zoom and font settings */
h1 { font-size: 5vw; }
/* 2. px bounds: ignore the user's default font size */
h1 { font-size: clamp(32px, 1.5rem + 2.5vw, 56px); }
/* Better */
h1 { font-size: clamp(2rem, 1.5rem + 2.5vw, 3.5rem); }- Min larger than max. If MIN is greater than MAX, clamp() returns MIN, so the text never scales. This usually happens after copying values in the wrong order.
- Forgetting line-height. Use a unitless line-height (such as 1.2 for headings, 1.5 for body) so it scales with the fluid font size automatically.
- Fluid everything. Body text rarely needs a big range. 16px to 18px or 20px is plenty; headings benefit most.
- Ignoring the scrollbar. On desktop,
100vwgenerally includes the vertical scrollbar, so the computed size is slightly larger than the content width suggests. It's rarely noticeable but explains small mismatches with design files. - Skipping the edge checks. Always look at the min and max viewport, plus a narrow phone in landscape, where long headings can still overflow.
Fluid vs breakpoint-based steps
h1 { font-size: 2rem; }
@media (min-width: 48em) { h1 { font-size: 2.75rem; } }
@media (min-width: 80em) { h1 { font-size: 3.5rem; } }| Fluid (clamp) | Breakpoint steps | |
|---|---|---|
| Transitions | Smooth, no jumps | Sudden changes at each breakpoint |
| Amount of CSS | One line per size | One rule per breakpoint per size |
| Predictability | Exact size depends on viewport | A few known sizes, easy to spec |
| Design handoff | Needs min and max values | Matches fixed mockups directly |
| Zoom behavior | Good if rem is used; weaker with vw alone | Good with rem sizes |
You don't have to pick one. Many sites use fluid sizing for headings and spacing, and keep body text at a fixed rem size. Breakpoints can still handle layout changes, such as moving from one column to two.
Fluid spacing uses the same maths
The clamp() technique isn't limited to text. Section padding, gaps and margins can use the same formula, for example padding-block: clamp(2rem, 1rem + 5vw, 6rem). Using the same viewport range for type and spacing keeps the rhythm of the page consistent at every width.
Container query units
If a component's size depends on its container rather than the window, container query units like cqi (1% of the container's inline size) can replace vw in the formula. They're supported in current major browsers, but the same accessibility rule applies: keep a rem component and rem bounds.
Frequently asked questions
What is fluid typography?
Fluid typography is text that scales smoothly with the viewport width between a minimum and maximum size, instead of jumping between fixed sizes at breakpoints. In CSS it is usually written with clamp().
How do I calculate the clamp() values for fluid type?
Pick a minimum and maximum font size and the viewport widths where each should apply. The slope is (max size − min size) ÷ (max width − min width); multiply it by 100 for the vw value, and the intercept is min size − slope × min width, converted to rem.
Is fluid typography bad for accessibility?
It can be if the middle value uses vw alone, because vw does not grow with zoom or user font settings. Use rem for the minimum, the maximum and part of the middle value, and keep the maximum no more than about 2.5 times the minimum.
Should body text be fluid?
Often only slightly. A small range like 16px to 18px is common for body copy, while headings benefit most from fluid sizing. Large changes in body size can hurt line length and readability.