Entry 06

One fluid scale for type and space

Use it when a design has more than two or three font sizes. Six numbers describe every size, and the browser derives the steps. It replaces a table of sizes per breakpoint, and it replaces a generator that someone has to find and run again.

Open
Make the frame narrower. Every size shrinks along a straight line, and stops changing below 320px and above 1240px. At 1240px or wider the readout says body 20px, title 48.83px and padding 40px. At 320px it says 18px, 37.32px and 18px.
Code CSS · 35 lines
:root {
  --narrow: 20;
  --wide: 77.5;
  --size-narrow: 1.125;
  --size-wide: 1.25;
  --ratio-narrow: 1.2;
  --ratio-wide: 1.25;

  --fluid: clamp(0rem, (100vw - var(--narrow) * 1rem) / (var(--wide) - var(--narrow)), 1rem);
  --at-narrow: calc(var(--size-narrow) * (1rem - var(--fluid)));
  --at-wide: calc(var(--size-wide) * var(--fluid));

  --step--1: calc(var(--step-0) / var(--ratio-narrow));
  --step-0: calc(var(--at-narrow) + var(--at-wide));
  --step-1: calc(var(--at-narrow) * var(--ratio-narrow) + var(--at-wide) * var(--ratio-wide));
  --step-2: calc(var(--at-narrow) * pow(var(--ratio-narrow), 2) + var(--at-wide) * pow(var(--ratio-wide), 2));
  --step-3: calc(var(--at-narrow) * pow(var(--ratio-narrow), 3) + var(--at-wide) * pow(var(--ratio-wide), 3));
  --step-4: calc(var(--at-narrow) * pow(var(--ratio-narrow), 4) + var(--at-wide) * pow(var(--ratio-wide), 4));

  --space-s: var(--step-0);
  --space-m: calc(1.5 * var(--step-0));
  --space-l: calc(2 * var(--step-0));
  --space-s-l: calc(var(--at-narrow) + 2 * var(--at-wide));
}

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

.card {
  display: grid;
  gap: var(--space-s);
  padding: var(--space-s-l);
}
Why it works, the rules and the credits 5 reasons · 7 rules · support · credits

Why it works

  1. The first six lines are the whole design decision. Widths and sizes are in rem, written as plain numbers, so 20 is 320px and 1.125 is 18px. Body text runs from 18px on a 320px screen to 20px on a 1240px one, and each heading step is 1.2 times the last on the phone and 1.25 times on the desktop.
  2. --fluid is a length that is 0 at the narrow width, 1rem at the wide width and a straight line between. It holds the only clamp(), and that one bounds every token.
  3. --at-narrow and --at-wide are the two base sizes, each weighted by how close the viewport is to its width. Their sum is step 0.
  4. pow() raises each ratio to the step number. A step is the narrow scale's size times one weight plus the wide scale's size times the other, which is the line Fluid sizes with clamp() draws between two points.
  5. A space token is a multiple of step 0, so it grows as much as body text does. A pair such as --space-s-l gives the two terms different multipliers. It is the small size on the phone and the large size on the desktop, 18px to 40px here.

Rules

  1. Write the inputs as plain numbers. With 20rem and 77.5rem the --fluid line divides a length by a length, and Firefox does not support that.
  2. Change an input in the rule that declares the steps. A step takes its value where it is declared, so a ratio set on a descendant changes nothing below it.
  3. Derive sizes below step 0 from step 0 and the narrow ratio, as --step--1 does. With both ratios a small size shrinks as the screen grows.
  4. Check the top step against the limit in Fluid sizes with clamp(). Step n passes when (size-wide / size-narrow) × (ratio-wide / ratio-narrow)ⁿ is 2.5 or less. The example gives 1.31 at step 4.
  5. Write a pair only where a layout uses it, and never use one for a font size. A pair is steep on purpose and breaks that limit.
  6. For a scale that follows the container, write 100cqi in place of 100vw. Container queries with container units has the rules.
  7. Components use the tokens and never repeat the math.

Support

Chrome
120
Firefox
118
Safari
15.4

Set by pow(). Tested in Chrome 150 and real Safari 27 on 2026-10-01, where every step came out within 0.001px of the formula.

Credits

Borrowed from: James Gilyead, Designing with fluid type scales, 2020-02-01, for the two scales · Trys Mudford, CSS-only fluid modular type scales, 2020-02-02, and Fluid custom properties, 2020-02-03 · James Gilyead, Designing with a fluid space palette, 2021-03-16, for space pairs. Utopia prints one finished clamp() per step from a calculator, because CSS had no powers in 2020. With pow() the browser does that math.

Background: Miriam Suzanne, Responsive and Fluid Typography with Baseline CSS Features, 2025-12-16

Docs: MDN: pow() · Utopia type calculator