Chapter 15: Fluid Type and Space
Using clamp(), min(), max(), and Utopia scales for fluid typography and spacing

Dania Rifki
This is where the "fluid canvas" philosophy becomes practical. Instead of setting fixed font sizes and spacing values, we'll create scales that smoothly adapt to any viewport size.
The problem with fixed values
If you set h1 { font-size: 3rem; }, that heading is 3rem everywhere. On a wide monitor, it might look fine. On a phone, it's comically large. The old solution was media queries:
/* The old way */
h1 {
font-size: 2rem;
}
@media (min-width: 768px) {
h1 {
font-size: 2.5rem;
}
}
@media (min-width: 1200px) {
h1 {
font-size: 3rem;
}
}
This works, but it creates jumps at each breakpoint. The font size snaps from one value to another. And you end up writing three rules for every element. For a whole type scale and spacing system, that's a lot of repetitive code.
The clamp() function
clamp() takes three values: a minimum, a preferred value, and a maximum:
h1 {
font-size: clamp(2rem, 5vw + 1rem, 3.5rem);
}
A quick note on units: vw stands for viewport width, 1vw is 1% of the browser window's width. So 5vw on a 1000px-wide window is 50px. CSS can do math with mixed units like 5vw + 1rem; the browser calculates the result automatically.
This says: "Make the font size 5vw + 1rem (which scales with the viewport), but never let it go below 2rem or above 3.5rem." Think of it like a levels adjustment in Photoshop: the value scales freely in the middle range, but is clamped at both ends.
The result is a font size that smoothly scales between the min and max as the viewport changes. No breakpoints. No jumps. Pure fluid scaling.
A heading using clamp() inside a resizable container. Drag to resize and watch the text scale fluidly between its minimum and maximum. (The demo uses cqw units so the text responds to the container rather than the viewport.)
min() and max()
These helper functions are useful companions to clamp():
/* Width is 100% of its container, but never more than 60rem */
.wrapper {
width: min(100%, 60rem);
}
/* Padding is at least 1rem, but grows with the viewport */
.section {
padding: max(1rem, 3vw);
}
clamp(MIN, VAL, MAX) is actually equivalent to max(MIN, min(VAL, MAX)), but clamp() is easier to read.
The Utopia approach
Calculating clamp() values by hand is tedious and error-prone. This is where Utopia comes in. Utopia is a set of tools that generates fluid type scales and spacing scales for you, based on:
- A minimum viewport width (e.g., 320px for small phones)
- A maximum viewport width (e.g., 1240px for large screens)
- A base font size at each extreme
- A type scale ratio (like a musical scale for font sizes)
You plug in your design decisions, and Utopia outputs a set of clamp() values as custom properties. For example, a generated type scale might look like this:
:root {
/* Type scale generated by utopia.fyi */
--step--1: clamp(0.8rem, 0.73rem + 0.33vw, 1rem);
--step-0: clamp(1rem, 0.87rem + 0.56vw, 1.33rem);
--step-1: clamp(1.25rem, 1.03rem + 0.88vw, 1.78rem);
--step-2: clamp(1.56rem, 1.21rem + 1.31vw, 2.37rem);
--step-3: clamp(1.95rem, 1.41rem + 1.89vw, 3.16rem);
--step-4: clamp(2.44rem, 1.63rem + 2.68vw, 4.21rem);
}
--step-0 is your base body text size. Negative steps are smaller (small print, captions). Positive steps are larger (headings). Each step increases by a consistent ratio, creating a harmonious typographic scale.
Using them is straightforward:
body {
font-size: var(--step-0);
}
h1 {
font-size: var(--step-4);
}
h2 {
font-size: var(--step-3);
}
h3 {
font-size: var(--step-2);
}
small {
font-size: var(--step--1);
}
Fluid space
Utopia applies the same principle to spacing. A fluid space scale gives you consistent, proportional spacing that scales with the viewport:
:root {
/* Space scale generated by utopia.fyi */
--space-3xs: clamp(0.25rem, 0.21rem + 0.19vw, 0.38rem);
--space-2xs: clamp(0.5rem, 0.43rem + 0.28vw, 0.67rem);
--space-xs: clamp(0.75rem, 0.65rem + 0.47vw, 1.04rem);
--space-s: clamp(1rem, 0.87rem + 0.56vw, 1.33rem);
--space-m: clamp(1.5rem, 1.3rem + 0.84vw, 2rem);
--space-l: clamp(2rem, 1.74rem + 1.13vw, 2.67rem);
--space-xl: clamp(3rem, 2.61rem + 1.69vw, 4rem);
--space-2xl: clamp(4rem, 3.48rem + 2.25vw, 5.33rem);
}
You also get space pairs for larger jumps, like --space-s-l which smoothly scales between the s value at the minimum viewport and the l value at the maximum. These are great for section padding that should be compact on phones and generous on large screens.
Use the spacing tokens everywhere. Notice that the .flow fallback evolves: in Chapter 11 we used 1em as a simple starting point; now we upgrade it to var(--space-s) so it participates in the fluid space system:
.flow > * + * {
margin-block-start: var(--flow-space, var(--space-s));
}
section {
padding-block: var(--space-l-xl);
}
.grid {
gap: var(--space-m);
}
Using the Utopia calculators
Visit utopia.fyi/type for the type scale calculator and utopia.fyi/space for the space scale. The calculators let you adjust:
- Minimum and maximum viewport widths
- Base font sizes at each extreme
- Scale ratios (minor third, major third, perfect fourth, etc.)
- The number of steps in each direction
The ratio names will feel familiar if you have any music background. A "minor third" ratio (1.2) creates a gentle, subtle scale. A "perfect fourth" (1.333) is more dramatic. A "golden ratio" (1.618) creates strong contrast between steps.
Experiment with the calculators, preview the results, then copy the generated CSS custom properties into your project. That's your type and space system done.
The big picture
With Utopia's scales defined as custom properties, you now have a complete, fluid design system:
- Colors from your palette (Chapter 13)
- Type sizes that scale fluidly (
--step-*) - Spacing that scales fluidly (
--space-*) - Font families and line-heights
Everything is defined as tokens. Everything scales smoothly. No breakpoints needed for sizing. This is the practical result of the "content is like water" philosophy.