Published October 19, 2026
How do you build a fluid type scale with clamp() and a modular ratio? You let the ratio pick the sizes and clamp() make them fluid. A modular scale multiplies one base size by one ratio, step after step; clamp() then pins each step between a minimum and a maximum. The finished product looks like this:
:root {
--step--2: clamp(0.78rem, 0.77rem + 0.03vw, 0.80rem);
--step--1: clamp(0.94rem, 0.94rem + 0.00vw, 0.94rem);
--step-0: clamp(1.13rem, 1.08rem + 0.22vw, 1.25rem);
--step-1: clamp(1.35rem, 1.24rem + 0.55vw, 1.67rem);
--step-2: clamp(1.62rem, 1.41rem + 1.05vw, 2.22rem);
--step-3: clamp(1.94rem, 1.59rem + 1.77vw, 2.96rem);
--step-4: clamp(2.33rem, 1.77rem + 2.81vw, 3.95rem);
--step-5: clamp(2.80rem, 1.94rem + 4.28vw, 5.26rem);
}
That is the real output of the Utopia type generator for a 320 to 1240 viewport range with 1.2 and 1.333 ratios, quoted from LogRocket's walkthrough of the tool. Eight steps, zero media queries, and every size is in ratio with its neighbors. The rest of this guide is how that map gets made, so you can build your own instead of copying someone's.
The modular scale in 30 seconds
A modular scale is multiplication. Take a base size, pick a ratio, and multiply up for display type or divide down for small print. With a 16px base and a 1.25 ratio: 16, 20, 25, 31.25, 39.06, 48.83. Each step is the previous step times 1.25, which is why the scale looks designed instead of eyeballed. The classic ratios have names borrowed from music: 1.067 is a minor second, 1.125 a major second, 1.2 a minor third, 1.25 a major third, 1.333 a perfect fourth. Larger ratios feel more dramatic; smaller ones feel more sober. There is no correct answer here. Pick 1.25 and move on. You can feel the difference between ratios only by shipping one.
Why the fluid type scale needs 2 ratios
One ratio gives you a static scale. The fluid trick is using a smaller ratio at the small viewport and a larger ratio at the large viewport. Utopia defaults to 1.2 at the minimum and 1.333 at the maximum, and the reasoning is sound: on a 320px phone, body text and headings should not be far apart or the layout falls apart, while on a 1240px desktop there is room for display type to be properly dramatic. Look at step 5 in the map above: it runs from 2.80rem at the small end to 5.26rem at the large end. The max is nearly double the min, because the 1.333 ratio compounded 5 times outruns the 1.2 ratio compounded 5 times. That widening gap is the whole point. If you used one ratio everywhere, every step would scale by the same factor and the scale would look stiff.
From ratios to clamp()
Each step hands you a minimum and a maximum, and each pair becomes one clamp() value through the usual interpolation: the preferred value is a line connecting (min viewport, min size) to (max viewport, max size). You do not need to hand-write the line. Feed each step's min and max into a clamp generator and it returns the middle expression. What you do need to decide is the naming and the mapping. Utopia's --step-N convention works well: --step-0 is body text, positive steps go up, negative steps go down. Then map the steps to your elements:
body { font-size: var(--step-0); }
h1 { font-size: var(--step-5); }
h2 { font-size: var(--step-4); }
h3 { font-size: var(--step-3); }
small { font-size: var(--step--1); }
That h1 through h3 mapping is the one Andy Bell uses on piccalil.li, and it has survived because 5 display steps cover every heading a normal page needs. Storing the whole clamp() in the custom property, instead of storing min and max separately, keeps the usage sites dead simple: one var() per font-size.
The 3 mistakes that break a scale
1. Flipping the ratios. If the small-viewport ratio is larger than the large-viewport ratio, your negative steps invert: the minimum ends up bigger than the maximum and clamp() silently pins everything to the wrong value. Keep the min ratio at or below the max ratio and this never happens.
2. Too many steps. Past 7 or 8, the scale starts inventing distinctions no reader can see, and you end up with --step-6 and --step-7 that differ by a rounding error. Six steps is the sweet spot for most sites: small, body, and 4 display sizes. The scale only earns its keep if you actually use every step, so count your real heading levels before you generate.
3. px-based preferred values. A clamp() with a px preferred value stops responding to the user's font-size setting, which defeats the point of fluid type for anyone who zooms or bumps their default size. Keep the preferred value in rem and let vw do the viewport work.
Generate your steps: Use the CSS clamp calculator to build each step of your fluid type scale, then export the whole set as custom properties or Tailwind tokens.
Pick 1.25, generate the steps, and ship the scale you made. The one you ship beats the perfect one you keep tuning.
Frequently asked questions
What is a fluid type scale?
A set of font sizes generated from a modular ratio, where each step is a CSS clamp() value with a minimum, a fluid preferred value, and a maximum. The scale keeps sizes in proportion while every size responds to the viewport.
Which modular ratio should I use with clamp()?
1.25 (a major third) is a safe default. Use 1.2 for restrained, editorial type and 1.333 or higher for dramatic display type. For the fluid version, use the smaller ratio at the small viewport and the larger ratio at the large viewport, as Utopia does with 1.2 and 1.333.
How many steps should a fluid type scale have?
6 steps covers most sites: one small size, body text, and 4 display sizes. Past 7 or 8 steps, adjacent sizes differ by amounts nobody can see, so match the step count to your actual heading levels.
Can I use a fluid type scale with Tailwind?
Yes. Export each step as a CSS custom property and reference it in Tailwind arbitrary values like text-[var(--step-3)], or extend your Tailwind theme's fontSize with the clamp() values so you get utilities like text-step-3.
Why does my fluid type scale need 2 ratios?
One ratio scales every step by the same factor, which looks stiff. A smaller ratio at the small viewport and a larger ratio at the large viewport lets display type open up on desktop while body text stays calm on mobile.
Prefer this by email? Get future guides in your inbox.
Note: Code examples follow the CSS Values and Units Module Level 4 grammar and were checked against current browser behavior. Test in your target browsers before shipping.