There’s a particular kind of embarrassment reserved for the moment you open your beautifully crafted web page on a 13-inch laptop and watch all your carefully considered heading sizes either cramp into something tiny or balloon out past the content container. Fixed type scales do this. They always have. Fluid typography using CSS clamp() solves it, and I’m genuinely baffled that, as of 2026, it still isn’t the default approach on most UK frontend projects I encounter.
This isn’t a gentle introduction. I’ll assume you know what a rem is and that you’ve written a media query before. What I want to walk through is the actual maths behind clamp(), how to calibrate it for the screen sizes your UK users are actually on, and how to fold the whole thing into a proper design token workflow so your type scale lives in one place and propagates everywhere.

What clamp() actually does
clamp(min, preferred, max) picks the preferred value, but clamps it between a floor and a ceiling. That preferred value is where all the cleverness happens. If you write something like:
font-size: clamp(1rem, 2.5vw, 1.5rem);
You get a font size that scales with the viewport width, but never drops below 1rem and never exceeds 1.5rem. That’s the core idea. The problem is that 2.5vw in isolation is a terrible preferred value, because it produces 0 at 0px and scales purely linearly with zero regard for legibility at mid-range viewports. You need a preferred value that interpolates smoothly between two known sizes at two known viewport widths. That’s where the proper formula comes in.
The interpolation formula you actually need
The formula for a linearly interpolated fluid value between a minimum font size at a minimum viewport width and a maximum font size at a maximum viewport width looks like this:
preferred = calc(minSize + (maxSize - minSize) * ((100vw - minWidth) / (maxWidth - minWidth)))
In real units, if you want 1rem (16px) at 375px viewport and 1.5rem (24px) at 1280px:
slope = (24 - 16) / (1280 - 375) = 8 / 905 ≈ 0.00884
intercept = 16 - 0.00884 * 375 ≈ 12.685px
preferred = calc(12.685px + 0.00884 * 100vw)
Which you’d convert to rem (dividing by 16 for a standard root font size) and write as:
font-size: clamp(1rem, 0.7928rem + 0.5525vw, 1.5rem);
Yes, those decimals look gnarly. That’s fine. CSS handles it. You are not writing this by hand for every token; you’re generating it. More on that shortly.
Calibrating for UK screen usage patterns
Your breakpoint assumptions matter here. The temptation is to use 320px as your minimum, but StatCounter’s UK mobile resolution data shows that sub-360px devices now represent a tiny fraction of UK traffic. The iPhone 15 family, Samsung Galaxy A series, and Pixel 8 all sit at 390px or 393px logical width. Using 375px as your minimum is reasonable; 360px is safer if you want extra headroom.
At the upper end, UK desktop usage clusters heavily around 1280px to 1440px. A 1920px maximum makes sense for large-display contexts, but for most B2B SaaS products and editorial sites, capping your fluid scale at 1440px stops runaway sizes on widescreen monitors whilst keeping the type relationship intact on the screens your users actually have.
My working defaults for a UK web project in 2026:
- Min viewport:
375px - Max viewport:
1440px - Root font size assumption:
16px
These feed into every token calculation. Change the viewport bounds and every size updates automatically, which is exactly the kind of systematic control that makes a design token workflow worthwhile.

Building a fluid type scale as design tokens
The right place to store a fluid type scale is in CSS custom properties, generated from a source of truth. I’d argue for keeping the raw scale parameters in a JSON token file (compatible with the W3C Design Token Community Group format, which has decent tooling support now) and compiling the clamp() values at build time.
A minimal token definition might look like:
{
"font-size": {
"sm": { "min": "14px", "max": "16px" },
"base": { "min": "16px", "max": "18px" },
"lg": { "min": "20px", "max": "26px" },
"xl": { "min": "28px", "max": "40px" },
"2xl": { "min": "36px", "max": "56px" }
}
}
A small Node script (or a PostCSS plugin like postcss-utopia, which wraps the Utopia calculator logic) reads those pairs plus your viewport bounds and emits:
:root {
--font-size-sm: clamp(0.875rem, 0.8279rem + 0.2347vw, 1rem);
--font-size-base: clamp(1rem, 0.9529rem + 0.2347vw, 1.125rem);
--font-size-lg: clamp(1.25rem, 1.0735rem + 0.8825vw, 1.625rem);
--font-size-xl: clamp(1.75rem, 1.3676rem + 1.9118vw, 2.5rem);
--font-size-2xl: clamp(2.25rem, 1.6912rem + 2.7941vw, 3.5rem);
}
Then your component CSS just references var(--font-size-xl) and the browser handles the rest. No media queries, no breakpoint logic in component files, no manual tweaks per screen size. If you’re already building with a considered typography stack, dropping fluid tokens in is a natural extension of the same thinking.
The line-length problem fluid type creates
Here’s a wrinkle I see trip people up. When you make your body copy larger at wide viewports, your line length (measure) also tends to grow because content areas expand. Optimal legibility sits around 60-75 characters per line. A 18px body size in a full-width column at 1440px is brutal to read.
The fix is to apply fluid typography alongside a max-width constraint on text containers, usually expressed in ch units. Something like max-width: 72ch on a prose container gives you typographic legibility that holds across viewport sizes. If you’re working on data-dense interfaces, this interacts with layout in more complex ways, but for editorial contexts it’s the single most effective pairing. I’ve written separately about designing data-heavy interfaces where these constraints get more nuanced.
Accessibility: what clamp() doesn’t fix for you
This is the part people skip and shouldn’t. clamp() with viewport-relative units can break user font-size preferences when set as the preferred value. If a user has set their browser default to 20px (common amongst users with low vision), a clamp() with a vw-based preferred value will often override that preference at mid-range viewport widths.
The fix is to express your min and max in rem (so they respect the user’s root font size) and keep the viewport-scaling component relatively modest. Avoid making the vw component so large that it dominates and overrides rem-based preferences at most widths. The WCAG 1.4.4 Resize Text criterion requires that text can be resized to 200% without loss of content or functionality, and a poorly calibrated clamp() can fail that silently.
Test with browser zoom, not just OS-level zoom. They behave differently. And if you’re working on products where accessibility really matters (and it should always matter), cross-reference your approach against the guidance in the designing for older users piece, since fluid type scales interact directly with the readability concerns raised there.
A practical integration checklist
Before you ship a fluid type scale, I’d run through these:
- Are min and max values in
rem, notpx? (pxignores user preferences.) - Does the scale still look right at 320px? (Edge case, but government accessibility audits will check it.)
- Have you tested browser zoom at 200%? That’s the WCAG threshold.
- Are line lengths constrained at wide viewports?
- Do your fluid sizes live in CSS custom properties, not scattered throughout component files?
- Is the scale generated from a single source of truth so it can change in one place?
I’ve seen projects where the type scale is duplicated across twelve component files and the Figma file, and they’re all different from each other by 2px here and there. It’s the typographic equivalent of archaeological layers. A token-driven clamp() system collapses that mess into something you can actually maintain.
Worth mentioning: Utopia and similar tools
If you’d rather not write a custom build script, Utopia.fyi by Clearleft is the most polished UI for generating fluid type and space scales. You punch in your viewport bounds, your type scale ratios, and your base sizes, and it spits out ready-to-use clamp() values with a live preview. I use it for rapid prototyping and to sanity-check hand-calculated values. It’s also where I first saw how naturally fluid grids and fluid type pair together, which led me down the rabbit hole of the kind of structured layout thinking covered in the return to editorial grid layout discussion.
On completely different creative projects, I’ve noticed that even hobbyist communities with strong visual identity thinking are getting more thoughtful about typography at scale. The folks at brickclub.uk are a good example of a community site that clearly cares about readability across devices, which is the baseline any content-led site should be hitting.
Fluid typography isn’t a trend. It’s just correct behaviour for a medium where you genuinely do not know the screen your user is on. The tooling is mature, the browser support is universal (even IE is no longer an excuse anyone’s making in 2026), and the design token integration path is well-trodden. The only reason not to be doing this is inertia, and inertia is a terrible technical decision.
Frequently Asked Questions
What browsers support CSS clamp() for fluid typography?
All modern browsers have supported clamp() since 2020, including Chrome, Firefox, Safari, and Edge. As of 2026 global browser support sits above 97%, so there are no meaningful compatibility concerns for UK web projects. You can use it without a fallback for the vast majority of users.
How is CSS clamp() different from using media queries for responsive type?
Media queries produce stepped changes at fixed breakpoints, so font size jumps rather than scales. clamp() interpolates continuously between a minimum and maximum size as the viewport width changes, producing smooth scaling with no abrupt jumps. It also means far less code, since you replace multiple breakpoint overrides with a single property value.
Will fluid typography break user font size preferences in their browser?
It can, if you express the min and max values in px instead of rem. Using rem for both bounds ensures the scale respects a user’s browser default font size setting. Keep the viewport-scaling component proportionally modest so it doesn’t override user preferences at mid-range viewport widths.
