Episode 1 — Fundamentals / 1.9 — CSS Responsive Design
1.9.e — Fluid Typography
In one sentence: Fluid typography uses
clamp()to scale text smoothly between a minimum and maximum size as the viewport changes — eliminating jarring jumps at breakpoints and keeping readability locked in at every width.
Navigation: ← 1.9.d — Responsive Images · 1.9.f — Spacing & Rhythm →
1. The problem with fixed font sizes
Stepped approach (media queries only)
h1 { font-size: 1.5rem; }
@media (min-width: 768px) { h1 { font-size: 2rem; } }
@media (min-width: 1200px) { h1 { font-size: 3rem; } }
This creates jumps — at 767px the heading is 1.5rem, at 768px it snaps to 2rem. Between breakpoints the size is frozen, even though the viewport keeps changing.
Viewport-only approach (dangerous)
h1 { font-size: 5vw; }
Text scales continuously but has no bounds — at 320px it's 16px (barely readable), at 1920px it's 96px (absurdly large). Users who zoom the browser get no benefit because vw doesn't respond to zoom.
2. clamp() to the rescue
h1 {
font-size: clamp(1.5rem, 2.5vw + 0.5rem, 3rem);
}
How clamp(min, preferred, max) works
| Parameter | Role | Example |
|---|---|---|
| min | Floor — never go below this | 1.5rem (24px at default) |
| preferred | Scales with viewport | 2.5vw + 0.5rem |
| max | Ceiling — never go above this | 3rem (48px at default) |
Font size
3rem ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ← max
╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
╱ preferred value scales linearly
╱
1.5rem ─ ← min
├────────────┼────────────┼────────────┤
320px 768px 1200px 1920px
Viewport width
The value is clamped: below a certain viewport the min kicks in, above a certain viewport the max kicks in, and in between the preferred value slides smoothly.
3. Calculating the preferred value
The preferred value typically combines a viewport unit with a rem offset so that:
- The viewport unit provides fluid scaling.
- The rem offset ensures a reasonable base and responds to user zoom.
Manual formula
To scale from min-size at min-viewport to max-size at max-viewport:
preferred = viewport-slope × 100vw + rem-offset
where:
viewport-slope = (max-size - min-size) / (max-viewport - min-viewport)
rem-offset = min-size - (viewport-slope × min-viewport)
Example: Scale from 1.5rem (24px) at 320px to 3rem (48px) at 1200px:
slope = (48 - 24) / (1200 - 320) = 24 / 880 ≈ 0.02727
offset = 24 - (0.02727 × 320) = 24 - 8.727 = 15.273px ≈ 0.955rem
→ clamp(1.5rem, 2.727vw + 0.955rem, 3rem)
Don't memorize this — use a tool (see section 7).
4. Fluid type scale
A type scale is a set of harmonious sizes for body, headings, and small text. A fluid type scale makes every step scale smoothly.
:root {
--text-sm: clamp(0.8rem, 0.17vw + 0.75rem, 0.875rem);
--text-base: clamp(1rem, 0.34vw + 0.91rem, 1.125rem);
--text-lg: clamp(1.25rem, 0.56vw + 1.08rem, 1.5rem);
--text-xl: clamp(1.563rem, 0.89vw + 1.3rem, 2rem);
--text-2xl: clamp(1.953rem, 1.36vw + 1.56rem, 2.75rem);
--text-3xl: clamp(2.441rem, 2.01vw + 1.87rem, 3.5rem);
}
body { font-size: var(--text-base); }
h1 { font-size: var(--text-3xl); }
h2 { font-size: var(--text-2xl); }
h3 { font-size: var(--text-xl); }
.caption { font-size: var(--text-sm); }
5. Line length and readability
Fluid font sizes affect line length (ch or character count per line). Optimal reading is 45–75 characters per line.
.prose {
font-size: var(--text-base);
max-width: 65ch;
line-height: 1.6;
}
As font size scales up, ch units grow proportionally, keeping line length comfortable without extra breakpoints.
6. Accessibility: respecting user preferences
The rem boundary rule
Always set clamp() bounds in rem, not px. When a user sets their browser's base font size to 20px (for low vision), rem-based bounds scale up accordingly.
/* Good — respects user zoom */
h1 { font-size: clamp(1.5rem, 2.5vw + 0.5rem, 3rem); }
/* Bad — ignores user zoom for bounds */
h1 { font-size: clamp(24px, 2.5vw + 8px, 48px); }
WCAG 1.4.4 — Resize text
Content must be resizable to 200% without loss of functionality. clamp() with rem bounds passes this test because the bounds themselves scale with the user's zoom level.
Test it
- Open browser settings → set font size to "Very Large."
- Use
Ctrl+/Cmd+to zoom to 200%. - Verify all text scales and nothing overflows or becomes unreadable.
7. Tools for generating fluid type scales
| Tool | URL | What it does |
|---|---|---|
| Utopia | utopia.fyi | Generates fluid type + spacing scales with clamp() |
| Fluid Type Scale Calculator | fluid-type-scale.com | Visual slider for min/max size and viewport |
| Modern Fluid Typography | modern-fluid-typography.vercel.app | Interactive calculator with live preview |
| Type Scale | typescale.com | Traditional type scale (not fluid, but good for ratios) |
Utopia workflow:
- Set min viewport (e.g., 320px) and max viewport (e.g., 1200px).
- Choose a scale ratio (e.g., 1.2 minor third → 1.333 perfect fourth).
- Set min and max base sizes.
- Copy the generated
clamp()custom properties into your CSS.
8. Fluid sizing beyond typography
clamp() works for any length property:
/* Fluid padding */
.section {
padding-block: clamp(2rem, 5vw, 6rem);
}
/* Fluid gap */
.grid {
gap: clamp(1rem, 2vw, 3rem);
}
/* Fluid max-width */
.container {
max-width: clamp(320px, 90vw, 1200px);
margin-inline: auto;
}
9. Key takeaways
clamp(min, preferred, max)creates smooth, bounded scaling — no breakpoint jumps.- The preferred value mixes
vw+remfor viewport-responsive scaling that still respects zoom. - Set bounds in
remfor accessibility — neverpxbounds on text. - Build a fluid type scale with tools like Utopia instead of computing values by hand.
- Fluid techniques apply to spacing and layout, not just fonts.
Explain-It Challenge
Explain without notes:
- Why is
font-size: 5vwdangerous withoutclamp(), and what two problems does it create? - Walk through what happens to
clamp(1rem, 2vw + 0.5rem, 2rem)at viewport widths 320px, 800px, and 2000px. - Why must the min and max values in
clamp()usereminstead ofpxfor body text?
Navigation: ← 1.9.d — Responsive Images · 1.9.f — Spacing & Rhythm →