Tutorial · Beginner · 5 min
Fluid typography with clamp()
Fixed font-sizes force you to stack up breakpoints: one size for phones, a jump at a tablet
width, another at the desktop width. The text snaps between those sizes and looks awkward in between.
clamp() replaces the whole ladder — it grows the type smoothly with the viewport, clamped to a
minimum and a maximum, with no media queries at all.
1. The anatomy of clamp()
clamp() takes three values and always returns the middle one, kept inside the outer two:
clamp(MIN, PREFERRED, MAX). The browser computes PREFERRED, then refuses to let
it drop below MIN or rise above MAX. For type, MIN is the smallest
size you'll allow on narrow screens, MAX is the largest on wide ones, and
PREFERRED is the value that changes with the viewport.
h1 {
font-size: clamp(1.5rem, 1rem + 2vw, 3rem);
/* min preferred max */
} On a 320px-wide phone this resolves to 1.5rem (the floor catches it). On a very wide screen
it tops out at 3rem. Everywhere in between, the size tracks the window.
2. Why the preferred value is rem + vw
The interesting part is the middle argument: 1rem + 2vw. The vw unit is what makes
the text fluid — 1vw equals 1% of the viewport width, so 2vw adds 6.4px at 320px
and 30.72px at 1536px. That growing term is the scaling. The 1rem base is added on top, and it
matters for accessibility: a value built only from vw ignores the user's font-size setting and
breaks browser zoom, while a rem term stays tied to the root font-size and keeps zoom working.
To pick the vw number, think of PREFERRED as a straight line: it should equal the
MIN size at your smallest viewport and the MAX size at your largest. The slope is
the rise over the run. Say you want 1.5rem (24px) at 320px and 3rem (48px) at
1280px. The size rises 24px across 960px of width, so each pixel of width adds 24 / 960 = 0.025px.
One vw is 1% of width, so it adds width / 100 px — meaning 2.4vw
gives that 0.025px-per-pixel slope. Drop a rem base in front and trim the vw
slightly to keep the line passing through both targets; the exact arithmetic rarely needs to be perfect,
because MIN and MAX clamp the ends anyway.
/* grows from ~1.5rem on phones toward 3rem on desktops */
h1 { font-size: clamp(1.5rem, 1.1rem + 1.8vw, 3rem); } 3. Build a fluid type scale
Rather than write clamp() at every selector, define the scale once as custom properties on
:root. Each step gets its own min, preferred and max, so the whole hierarchy breathes together
as the window changes. Name them by step so the intent is obvious: --step-1 is smaller than
body text, --step0 is the body, and --step1 upward are the headings.
:root {
--step-1: clamp(0.83rem, 0.80rem + 0.15vw, 0.94rem); /* small print */
--step0: clamp(1.00rem, 0.95rem + 0.25vw, 1.13rem); /* body */
--step1: clamp(1.20rem, 1.10rem + 0.50vw, 1.50rem); /* h3 */
--step2: clamp(1.44rem, 1.25rem + 0.95vw, 2.00rem); /* h2 */
--step3: clamp(1.73rem, 1.40rem + 1.65vw, 2.67rem); /* h1 */
}
body { font-size: var(--step0); }
h1 { font-size: var(--step3); }
h2 { font-size: var(--step2); }
h3 { font-size: var(--step1); }
small{ font-size: var(--step-1); } Now there's a single source of truth for sizing. Change a step once and every element using it follows. Because the steps share a consistent ratio, the relationship between heading and body stays balanced at every width.
4. Keep it accessible
Two rules keep a fluid scale usable. First, always keep a rem term in the preferred value.
A pure-vw size like font-size: 4vw looks fine until someone zooms or raises their
default font-size — it won't respond, and that's a real barrier for low-vision users. The rem
base is what restores that control.
Second, pick a MIN that stays readable on the narrowest phone and a MAX that
doesn't overflow on a wide monitor. A MIN below about 1rem for body text gets hard
to read; a MAX that's too tall makes headings wrap clumsily. Test by resizing the window slowly
from 320px up — the text should glide, never jump, and stay comfortable at both ends.
/* body text: never below 1rem, gentle growth, capped at 1.13rem */
body { font-size: clamp(1rem, 0.95rem + 0.25vw, 1.13rem); } Where to take it next
The same approach works for spacing — clamp your section padding and gaps so the layout breathes with the
type. Pair a fluid scale with a sensible line-height (unitless, like 1.5 for body)
and your reading rhythm holds across every screen. For the heavy lifting on values you'd rather not compute
by hand, reach for our CSS tools, and browse more tutorials
when you're ready for the next technique.