Themes & Customization·5 min read·

Fluid typography with CSS clamp() — no media queries

Scale your blog's headings and body type smoothly from mobile to desktop in a single CSS line. Drop-in pattern for every theme.css and how to debug when clamp() picks the wrong viewport unit.

Most themes still ship with a media-query staircase for headings: 24px under 640px, 32px under 1024px, 48px above. That works, but the jumps between breakpoints feel sudden and you end up writing the same scale three times. The `clamp()` function replaces every staircase with a single, smooth equation.

The one-liner pattern

/* Hero headline — smooth from 32px on phone to 80px on desktop */
h1.hero {
  font-size: clamp(2rem, 1.2rem + 4vw, 5rem);
}

Translation: never smaller than 32px (2rem), never larger than 80px (5rem), and in between the size is `1.2rem + 4vw` — about 1.2× the viewport-relative growth rate of `vw` alone. The browser does the math on every resize.

A scale that works for blogs

For most VeloCMS themes I use this six-step scale in theme.css:

h1 { font-size: clamp(2rem, 1.4rem + 3vw, 3.5rem); }
h2 { font-size: clamp(1.5rem, 1.2rem + 1.5vw, 2.25rem); }
h3 { font-size: clamp(1.25rem, 1.1rem + 0.75vw, 1.625rem); }
h4 { font-size: clamp(1.1rem, 1rem + 0.5vw, 1.25rem); }
body, p, li { font-size: clamp(0.95rem, 0.9rem + 0.25vw, 1.0625rem); }
.small, time { font-size: clamp(0.8rem, 0.75rem + 0.1vw, 0.875rem); }

Drop this block at the top of `body[data-theme="<your-slug>"] { ... }` and remove every `@media (min-width:...) { h1 { font-size: ... } }` rule below.

When clamp() goes weird: the viewport-width gotcha

Some Safari versions had a bug where `clamp()` would pick the minimum value during page load and never recompute. If your hero text looks tiny on iOS until you rotate the device, add `min-height: 100vh` to a parent block — that forces a layout pass on first paint and clamp recalculates. Reported in Safari 15.x and earlier; fixed in 16.4+.

Debugging: paste this in DevTools

// Read the actual computed font-size every 100ms
setInterval(() => {
  const h1 = document.querySelector("h1");
  if (h1) console.log("h1 px:", getComputedStyle(h1).fontSize, "viewport:", innerWidth);
}, 100);

Resize the browser. Watch the px value scale continuously. If the px stays fixed but viewport changes, you've got a CSS specificity issue — some other rule is overriding `clamp()` with a hard value.