zudo-css-wisdom
GitHub repository

Type to search...

to open search from anywhere

Fluid Font Sizing with clamp()

The Problem

Responsive typography traditionally requires multiple media queries to adjust font sizes at different breakpoints. This creates abrupt jumps between sizes and produces verbose, hard-to-maintain CSS. AI agents frequently generate fixed px or rem values at arbitrary breakpoints instead of using fluid scaling, resulting in text that is either too small on mobile or too large on desktop, with jarring transitions between breakpoints.

The Solution

The CSS clamp() function enables fluid typography in a single line, smoothly scaling font sizes between a minimum and maximum based on viewport width. The syntax is clamp(min, preferred, max), where the preferred value typically combines a rem base with a vw component for viewport-relative scaling.

The Formula

The preferred (middle) value in clamp() should be calculated as a linear function of viewport width. The general formula is:

preferred = base-rem + (slope × 1vw)

Where the slope is derived from the desired font-size range and viewport range:

slope = (max-size - min-size) / (max-viewport - min-viewport)

Code Examples

Basic Fluid Typography

/* Body text: scales from 16px to 20px between 320px and 1200px viewports */
body {
  font-size: clamp(1rem, 0.909rem + 0.45vw, 1.25rem);
}

/* h1: scales from 28px to 48px */
h1 {
  font-size: clamp(1.75rem, 1.295rem + 2.27vw, 3rem);
}

/* h2: scales from 24px to 36px */
h2 {
  font-size: clamp(1.5rem, 1.227rem + 1.36vw, 2.25rem);
}

/* h3: scales from 20px to 28px */
h3 {
  font-size: clamp(1.25rem, 1.068rem + 0.91vw, 1.75rem);
}

Fluid Type Scale System

:root {
  /* Viewport range: 320px to 1200px */
  --step--1: clamp(0.833rem, 0.787rem + 0.23vw, 1rem);
  --step-0: clamp(1rem, 0.909rem + 0.45vw, 1.25rem);
  --step-1: clamp(1.2rem, 1.042rem + 0.79vw, 1.563rem);
  --step-2: clamp(1.44rem, 1.186rem + 1.27vw, 1.953rem);
  --step-3: clamp(1.728rem, 1.339rem + 1.95vw, 2.441rem);
  --step-4: clamp(2.074rem, 1.494rem + 2.9vw, 3.052rem);
}

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 Typography with clamp() — Resize viewport to see scaling

With Container Queries

Container query units (cqi, cqw, and the rest) apply the same clamp() pattern relative to a container's size instead of the viewport. See Container Query Units for the full unit reference.

/* Fluid sizing relative to container width instead of viewport */

/* Additive: rem base + cqi term */
.card-title {
  font-size: clamp(1rem, 0.5rem + 3cqi, 1.5rem);
}

/* Pure: cqi only, no rem base */
.card-title {
  font-size: clamp(1rem, 5cqi, 2rem);
}
PatternFormulaWhen to use
Additiveclamp(1rem, 0.5rem + 3cqi, 1.5rem)The rem base is a size floor independent of container width — text degrades gracefully even in a near-zero-width container. Use for headings that must stay legible at any container size.
Pureclamp(1rem, 5cqi, 2rem)Font size is directly proportional to the container's inline size (5cqi = 5% of it). Use when the container already has hard min/max width bounds set elsewhere in the CSS, and text should track the container's actual rendered size rather than degrade gracefully.

For fixed-composition stages (slides, deck viewers, fixed-aspect embeds), the pure pattern is not just an option but the correct contract — including how to derive uniform legibility floors and handle the zoom trade-off: see Stage-Proportional Typography.

To promote these one-off values into a component-scoped type and spacing token scale — and for the three rules that keep such a scale working (rem-dominant clamps for text-zoom accessibility, container-scoped consumption, fixed-px hairlines) — see Container Query Units as a Token Scale.

Common AI Mistakes

  • Using fixed px or rem values with media query breakpoints instead of clamp(), creating abrupt size jumps

  • Omitting the rem component in the preferred value (using only vw), which breaks zooming and accessibility

  • Setting minimum values too small (below 1rem / 16px for body text), making text unreadable on mobile

  • Not testing the formula at extreme viewport widths, leading to absurdly large text on ultrawide monitors

  • Using calc() with vw alone (e.g., calc(1rem + 1vw)) without upper or lower bounds, which clamp() naturally provides

  • Applying fluid sizing to every text element when only headings and display text benefit from it — body text often works fine at a fixed 1rem

  • Re-deriving a cqi coefficient from an old viewport-breakpoint font-size when migrating to container-query sizing, without checking whether that breakpoint value was correctly sized in the first place — a formula can be mathematically continuous across container widths and still render visibly wrong against the design's stated intent

When to Use

  • Headings and display text that need to scale between mobile and desktop

  • Type scale systems where every step should fluidly adjust

  • Hero sections with large text that needs to shrink on small screens

  • Any scenario where breakpoint-based font-size changes cause visible jumps

Avoid using clamp() for:

  • Body text where 1rem is perfectly adequate at all sizes

  • Text inside components with fixed dimensions

  • Situations where precise control at specific breakpoints is required

References

Revision History

CreatedUpdated