Eliminate breakpoint-based font size overrides with CSS clamp(). Scale text continuously from mobile to desktop using viewport units and build a fluid type scale token system.
Traditional responsive typography jumps between fixed sizes at breakpoints — 16px on mobile, 18px at 768px, 20px at 1200px. The result is abrupt size changes at arbitrary points. CSS clamp() gives you fluid, continuous scaling: text grows smoothly from a minimum to a maximum as the viewport widens, with zero breakpoints and zero size jumps. This lesson teaches you to calculate, implement, and systematise fluid type.
clamp(MIN, PREFERRED, MAX) evaluates PREFERRED and constrains it between MIN and MAX. When the viewport is narrow the font hits MIN and stops shrinking; when wide it hits MAX and stops growing; between the two extremes it scales smoothly. The PREFERRED value uses vw units — 1vw is 1% of viewport width. A preferred of 2.5vw means the font is 2.5% of viewport width: 25px at 1000px wide, 16px at 640px wide. The formula to find a preferred value that passes through two known points is: slope = (maxSize − minSize) / (maxViewport − minViewport); preferred = slope×100vw + (minSize − slope×minViewport/16)rem. Tools like utopia.fyi automate this for entire scales. The key insight: MIN is the floor you never go below, MAX is the ceiling you never go above, PREFERRED is the slope of the scaling curve between them.
1/* Body: 1rem at 320px viewport, 1.25rem at 1280px */
2body {
3 font-size: clamp(
4 1rem, /* MIN: 16px floor */
5 0.917rem + 0.26vw, /* PREFERRED: continuous slope */
6 1.25rem /* MAX: 20px ceiling */
7 );
8}
9
10/* H1: 2rem at 320px, 4.5rem at 1440px */
11h1 {
12 font-size: clamp(
13 2rem,
14 1.5rem + 2.5vw,
15 4.5rem
16 );
17}For body text scaling 1rem→1.25rem from 320px→1280px viewport:
slope = (1.25 − 1) / (1280 − 320) = 0.25 / 960 = 0.000260 rem/px
As a vw coefficient: 0.000260 × 100 = 0.026 (vw per pixel)
intercept = 1 − 0.000260 × 320 = 1 − 0.0833 = 0.917rem
preferred = 0.026vw × viewport + 0.917rem
Verify: at 320px: 0.026 × 3.2 + 0.917 = 0.083 + 0.917 = 1rem ✓
At 1280px: 0.026 × 12.8 + 0.917 = 0.333 + 0.917 = 1.25rem ✓
1/* Mathematically verified fluid values */
2body { font-size: clamp(1rem, 0.917rem + 0.26vw, 1.25rem); }
3h3 { font-size: clamp(1.2rem, 1.05rem + 0.47vw, 1.5rem); }
4h2 { font-size: clamp(1.44rem, 1.19rem + 0.78vw, 2rem); }
5h1 { font-size: clamp(1.73rem, 1.25rem + 1.5vw, 3rem); }
6
7/* Use utopia.fyi/type to generate a full coherent scale */A type scale is a set of harmonically related sizes. Rather than hard-coding each clamp() at the point of use, define them once as CSS custom properties and reference semantic names throughout your codebase. This makes global scale changes a single-line edit. A Perfect Fourth ratio (1.333) creates a comfortable scale for editorial content; Major Third (1.25) creates a tighter, more compact scale for dense UI.
1:root {
2 /* Perfect Fourth fluid scale — 320px to 1440px */
3 --text-xs: clamp(0.694rem, 0.663rem + 0.16vw, 0.80rem);
4 --text-sm: clamp(0.833rem, 0.796rem + 0.19vw, 0.958rem);
5 --text-base: clamp(1rem, 0.956rem + 0.22vw, 1.125rem);
6 --text-lg: clamp(1.333rem, 1.273rem + 0.30vw, 1.5rem);
7 --text-xl: clamp(1.777rem, 1.697rem + 0.40vw, 2rem);
8 --text-2xl: clamp(2.369rem, 2.263rem + 0.53vw, 2.667rem);
9 --text-3xl: clamp(3.157rem, 3.015rem + 0.71vw, 3.556rem);
10}
11
12body { font-size: var(--text-base); }
13caption { font-size: var(--text-xs); }
14small { font-size: var(--text-sm); }
15h4 { font-size: var(--text-lg); }
16h3 { font-size: var(--text-xl); }
17h2 { font-size: var(--text-2xl); }
18h1, .display { font-size: var(--text-3xl); }vw-based fluid type scales relative to the viewport — useful for full-page typography. But reusable components (cards, sidebars, modals) appear at different widths in different contexts. Container query units (cqi = container query inline size) make text scale relative to the component's container width rather than the viewport. A card title can be fluid relative to the card's width, regardless of where the card appears in the layout. The container must declare container-type: inline-size to establish a containment context.
1/* Establish containment context */
2.card {
3 container-type: inline-size;
4 container-name: card;
5 padding: 1.5rem;
6}
7
8/* Text scales with card width, not viewport */
9.card-title {
10 /* 1rem min, scales to 1.5rem as card grows */
11 font-size: clamp(1rem, 3cqi + 0.5rem, 1.5rem);
12}
13
14.card-body {
15 font-size: clamp(0.875rem, 2.5cqi + 0.4rem, 1rem);
16 line-height: 1.6;
17}WCAG 1.4.4 requires text to be resizable to 200% without loss of content. WCAG 1.4.12 (Text Spacing) mandates that line height can be set to 1.5× font size, spacing after paragraphs to 2× font size, and letter spacing to 0.12× font size without loss of content. These apply to user overrides, not fluid scaling. However, readability best practice demands: body text minimum 16px (1rem), small print minimum 12px (0.75rem). Always set clamp() MIN to at least these values, and always verify at 320px viewport width that no text drops below its minimum.
1/* WCAG-safe fluid body text */
2body {
3 /* MIN 1rem = 16px — WCAG body text minimum */
4 font-size: clamp(1rem, 0.917rem + 0.26vw, 1.25rem);
5 /* Line height must accommodate 1.5x scaling per WCAG 1.4.12 */
6 line-height: 1.6;
7}
8
9.caption {
10 /* MIN 0.75rem = 12px — acceptable for supplementary text */
11 font-size: clamp(0.75rem, 0.7rem + 0.16vw, 0.875rem);
12 /* Boost contrast for small text — aim for 7:1 ratio */
13 color: #595959;
14}
15
16/* To test: resize browser to 320px and verify no text is too small */A production-ready fluid type scale from 320px to 1440px with WCAG-safe minimums.
Next lesson covers modular type scales — Major Third, Perfect Fourth, and Golden Ratio — and how to implement them systematically to create visual hierarchy.
Your progress is saved in this browser, on this device only.