Fluid Typography with clamp()
Responsive text that scales smoothly between viewport sizes without media queries.
Copy-paste ready. No JavaScript. No build plugins. No "just one more utility class". WCAG-aware by default.
Live demo
36.5px (from clamp(24px, 16px + 2vw, 48px))19.1px (from clamp(16px, 14px + 0.5vw, 20px))Fluid heading
This paragraph scales smoothly between 16px and 20px. No media queries needed. The text size responds fluidly to the viewport width using CSS clamp().
💡 Real test: Use your browser's native zoom (Cmd/Ctrl + or -) to verify how the rem values actually scale with user preferences.
What is clamp()?
The clamp() CSS function lets you define a value that scales
between a minimum and a maximum based on a preferred value (usually something
viewport-relative).
Syntax: clamp(MIN, PREFERRED, MAX)
- MIN: Smallest allowed value (e.g.
1rem) - PREFERRED: Your "live" value, often including viewport units (e.g.
1rem + 2vw) - MAX: Largest allowed value (e.g.
3rem)
The browser evaluates the preferred value and then clamps it between min and max. No JavaScript. No media query sudoku. Just math.
Why fluid typography matters
Before clamp(), responsive typography usually meant one of two
bad options:
- Fixed sizes – text that is too small on mobile and headings that shout at you on large screens
- Media query hell – five breakpoints, ten
font-sizedeclarations, and nobody quite sure why the numbers are what they are
Fluid typography solves this with a single declaration:
h1 { font-size: clamp(1.5rem, 1rem + 2vw, 3rem); }
That heading now scales smoothly from 24px to 48px across viewport widths. No JavaScript. No breakpoint juggling. No drama.
Basic example
Here is a simple fluid heading that goes from 24px to 48px:
h1 { font-size: clamp(1.5rem, 1rem + 2vw, 3rem); /* * Small viewports: 24px (1.5rem) * Medium viewports: scales with viewport width * Large viewports: 48px (3rem) */ }
The middle value 1rem + 2vw does the work. It combines:
- A base size (
1rem) that respects user browser/OS text preferences - A scale factor (
2vw) that responds to viewport width
The result: text that feels calm on small screens and doesn't become ridiculous on large ones.
How to calculate clamp() values
If you like formulas, the PREFERRED part can be expressed as:
PREFERRED = BASE_SIZE + (SCALE_FACTOR × viewport_unit)
A concrete example:
font-size: clamp(1rem, 0.875rem + 0.5vw, 1.25rem);
/* ↑ ↑ ↑ ↑
* MIN BASE SCALE MAX
*/
Practical approach:
- Pick your minimum size (usually small devices), e.g.
1rem - Pick your maximum size (large desktop), e.g.
1.25rem - Keep the difference reasonable (4–8px for body text, more for headings)
- Choose a conservative
vwfactor (0.25–0.75). You want fluid, not Vegas zoom. - Adjust the base so that your minimum size is reached roughly where you expect most users to be
Or the honest version: use a tool like Modern Fluid Typography and copy the output. The goal is reliable typography, not winning a "who can derive the formula from first principles" contest.
Typography scale with clamp()
Here is a complete, realistic type scale based on clamp():
:root { /* Body text: 16px → 18px */ --text-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem); /* Small text: 14px → 16px */ --text-sm: clamp(0.875rem, 0.85rem + 0.125vw, 1rem); /* Large text: 18px → 22px */ --text-lg: clamp(1.125rem, 1rem + 0.5vw, 1.375rem); /* Headings */ --text-xl: clamp(1.25rem, 1rem + 1vw, 1.875rem); --text-2xl: clamp(1.5rem, 1rem + 2vw, 2.25rem); --text-3xl: clamp(1.875rem, 1.5rem + 2.5vw, 3rem); --text-4xl: clamp(2.25rem, 1.5rem + 3.5vw, 3.75rem); } body { font-size: var(--text-base); } h1 { font-size: var(--text-4xl); } h2 { font-size: var(--text-3xl); } h3 { font-size: var(--text-2xl); }
Drop this into :root, wire it into your design tokens, and you
have a coherent, scalable type system across devices.
Performance
Fluid typography with clamp() is boringly efficient:
- No JavaScript – no resize listeners, no custom layout logic
- No media query spaghetti – one declaration instead of four breakpoints
- Less CSS – fewer rules, fewer bytes shipped
- Native to the layout engine – the browser is built to do this kind of math
This is not a clever "CSS trick". It's a boring, built-in feature the layout engine is already very good at.
Accessibility considerations
Fluid typography is generally good for readability, but there are a few things to keep in mind:
-
Don't use pure viewport units for text – always combine them with min/max via
clamp(). WCAG 1.4.4 requires text to be resizable up to 200% without loss of content or functionality. -
Test with browser zoom – your
rem-based min/max values should scale with user preferences. That is a feature, not a bug. -
Maintain contrast ratios – WCAG AA requires 4.5:1 for normal text and 3:1 for large text (18px+ or 14px bold).
-
Avoid aggressive scaling – don't start body text at 12px and end it at "presentation mode on a cinema screen".
The demo at the top respects these rules. Try zooming your browser to 200% –
the sizes stay within sensible bounds because min and max are defined in rem.
Browser support
clamp() has been supported in modern browsers for years:
- Chrome 79+
- Firefox 75+
- Safari 13.1+
- Edge 79+
Fallback is standard progressive enhancement:
h1 { font-size: 2rem; /* Fallback for ancient browsers */ font-size: clamp(1.5rem, 1rem + 2vw, 3rem); }
Browsers that do not support clamp() will use the fixed size.
Browsers that do will override it with the fluid version. No UA sniffing, no
custom logic.
Common pitfalls
❌ Using vw alone
/* Bad: Can become unreadably small or huge */ h1 { font-size: 5vw; }
At 320px viewport: 5vw = 16px – too small for a heading.
At 2560px viewport: 5vw = 128px – fine if you are designing a
billboard, less fine for an app.
✅ Always use clamp()
/* Good: Bounded and predictable */ h1 { font-size: clamp(1.5rem, 1rem + 2vw, 3rem); }
Now it scales nicely but never goes below 24px or above 48px. No surprises for users or designers.
❌ Too many breakpoints
/* Verbose and hard to maintain */ h1 { font-size: 1.5rem; } @media (min-width: 640px) { h1 { font-size: 2rem; } } @media (min-width: 768px) { h1 { font-size: 2.5rem; } } @media (min-width: 1024px) { h1 { font-size: 3rem; } }
Four breakpoints, eight lines of CSS, and nobody remembers why those exact font sizes were picked.
✅ One fluid declaration
/* Clean and self-documenting */ h1 { font-size: clamp(1.5rem, 1rem + 2vw, 3rem); }
One line. Zero breakpoints. Same visual result, but smooth instead of stepped.
When NOT to use clamp()
As with most nice things, there are cases where clamp() is not worth the ceremony:
-
Email templates and hostile environments
If you are fighting Outlook, ancient webviews, or PDFs rendered from HTML, stick to fixedpxorrem. Fluid typography is wasted on a rendering engine from 2007. -
Locked-down design systems
If your team has a hard, printed type scale ("H1 is 32px. Always.") and brand cares more about pixel-perfect screenshots than real devices,clamp()will mostly generate arguments, not value. -
Static surfaces with known dimensions
Kiosk UIs, slide decks, indoor screens with fixed resolutions – if the viewport never changes, a plainfont-sizeis fine. -
You just need one heading
If this is a one-off marketing hero and you are not building a reusable scale, you can absolutely get away with two media queries and go home.
You do not get extra points for using clamp() everywhere. Use it where it actually reduces complexity.
How to explain this to your designer
If your designer hears "fluid typography" and immediately pictures random font sizes, this usually helps:
-
"We still have min and max."
clamp()doesn't randomise your type scale. We agree on the smallest and largest sizes, and the browser only interpolates between them. -
"We can put the exact numbers in Figma."
The min and max values are justpx/remsizes. They can live in your design tokens, components, and documentation like any other type scale. -
"This removes breakpoints, not control."
Instead of fourfont-sizevalues across breakpoints, we have one definition that behaves the same way everywhere. -
"It makes QA less painful."
Fewer "H2 is 20px here and 22px there, is that on purpose?" bugs. The behaviour becomes predictable instead of stepping.
If they are still skeptical, start with body text and one heading. Once they see it behave nicely in a real build, the rest is usually an easy sell.
Try it yourself
The demo at the top of this page uses these exact techniques. Drag the viewport slider, watch the text size change, then copy the CSS and plug it into your own type scale.
.heading { font-size: clamp(1.5rem, 1rem + 2vw, 3rem); } .paragraph { font-size: clamp(1rem, 0.875rem + 0.5vw, 1.25rem); }
That is it. No framework magic. No build step tricks. Just CSS that does its job and lets you move on to more interesting problems.
Resources
- Modern Fluid Typography Calculator – generates
clamp()values for you - MDN: clamp() – the official documentation
- CSS-Tricks: Fluid Typography – a deeper dive into the technique
Copy. Paste. Ship. Boring reliability wins again.