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.