Responsive Typography
Responsive typography scales text fluidly between a minimum and maximum size as the viewport changes, most simply with the CSS clamp() function, so headings and body copy stay readable on every screen without a stack of media queries.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free HTML & CSS course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
By the end of this lesson you'll be able to make text that scales smoothly from phone to desktop with a single line of CSS, build a balanced type scale, set a comfortable line length, and do it all without breaking the zoom your low-vision readers rely on.
clamp() is a thermostat with a guaranteed range. You set a minimum the room can never fall below, a preferred setting that drifts with the weather outside (your viewport width), and a maximum it can never exceed. The temperature glides between those bounds on its own — you never have to step in for "if it's between 18° and 22°, do this."
Old-school responsive type was the opposite: a stack of if statements (media queries) that snapped the font from one fixed size to the next at hard breakpoints — comfortable at each chosen width, jumpy in between. clamp() replaces the staircase with a ramp. One line gives you a smooth, bounded size at every width.
Before you can size text responsively, you need to size it accessibly , and that comes down to the unit. A px is a fixed device pixel — it never changes. A rem is relative to the root font size (the html element), so 1rem follows whatever base size the user picked. An em is relative to the current element's font size, so it tracks its immediate context — and compounds when you nest things.
Run the worked example and watch the trap spring: the em heading nested inside another em heading gets huge, because each em multiplies the one above it.
clamp() takes three values and returns the middle one — unless it strays outside the bounds, in which case it's pinned to the nearest one. So clamp(2rem, 5vw, 4rem) means: aim for 5vw , but never smaller than 2rem and never larger than 4rem . It is literally max(min, min(preferred, max)) , just readable.
The pattern that keeps it accessible: a rem floor, a vw -based middle so it grows with the screen, and a rem ceiling. Because the floor and ceiling are in rem, browser zoom still enlarges the text — the thing a bare vw can't do. Resize the preview to see it glide.
Picking font sizes at random gives a page that feels off. A modular scale picks one base size and one ratio , then multiplies up: each step is the previous size times the ratio. A ratio of 1.25 (the "major third") is a calm, common choice — 1rem, 1.25rem, 1.563rem, 1.953rem, and so on.
Store the steps as custom properties so the scale lives in one place and every heading just reads a token. Wrap each one in clamp() and you get a scale that is both consistent (the ratio) and fluid (the clamp).
Size is only half of readable text. Line-height is the vertical space between lines — set it unitless (e.g. line-height: 1.6 ) so it multiplies the font size and scales with it; a px line-height won't. Measure is the line length, and the comfortable range is about 45–75 characters. Cap it with max-width: 65ch , where 1ch is the width of a "0" in your font.
Finally, text-wrap: balance evens out the lines of a heading so you don't get one orphan word on its own line, while text-wrap: pretty tidies the last lines of long paragraphs. Use balance for headings, pretty for body text.
People with low vision raise their browser's base font size or zoom the page, and accessibility guidelines require text to stay usable when zoomed to 200%. The golden rule: every font size must contain at least one rem (or em). A pure vw size ignores zoom entirely; a clamp() with rem bounds keeps growing.
Keep body text at a minimum of 1rem (16px), never pin the root size in a way that fights the user, and your fluid type stays friendly to everyone. The worked example puts a zoom-safe and a zoom-hostile heading side by side.
The heading below is frozen at one px size and the paragraph runs the full width. Convert the heading to a fluid clamp() and cap the paragraph's measure. Fill in the blanks marked ___ , then run it and check the comments.
Add the two missing scale tokens, point the headings at them, and tidy the ragged wrapping with text-wrap . Use the right keyword for each: balance for the heading, pretty for the paragraph.
Support is faded now — only an outline is given. Build a small article header that is fluid, consistently scaled, comfortably measured, and zoom-safe. Lean on the worked examples in sections 2–4 if you get stuck.
You can now build typography that's fluid, consistent and accessible. The essentials:
Next up: Flexbox Advanced Patterns , where you'll lay out these beautifully-sized elements into flexible, responsive components.
Practice quiz
What is a 'rem' unit relative to?
- The parent element's font size
- The viewport width
- The root (<html>) element's font size
- A fixed 12px
Answer: The root (<html>) element's font size. 1rem equals the root font size (16px by default), so rem-sized text respects the user's chosen base size — making it the go-to unit for fonts.
Why does 'em' compound when elements are nested?
- It is relative to the current element's font size, which multiplies down the tree
- It is relative to the viewport
- It ignores inheritance
- It is a bug in browsers
Answer: It is relative to the current element's font size, which multiplies down the tree. em is relative to the element's own font size, so an em inside an em multiplies (1.5 × 1.5 = 2.25×), making nested sizes grow unexpectedly.
What does clamp(min, preferred, max) compute?
- The average of the three values
- Always the preferred value
- The smallest of the three
- max(min, min(preferred, max)) — the preferred value bounded by a floor and ceiling
Answer: max(min, min(preferred, max)) — the preferred value bounded by a floor and ceiling. clamp() returns the preferred value but never lets it drop below min or rise above max — equivalent to max(min, min(preferred, max)).
Why is a bare 'font-size: 5vw' bad for accessibility?
- It is invalid syntax
- vw ignores browser zoom, so a low-vision user who zooms gets no larger text
- It only works on Chrome
- It makes text too small everywhere
Answer: vw ignores browser zoom, so a low-vision user who zooms gets no larger text. A pure vw value scales with window width but ignores zoom and font-size preferences, so zooming in doesn't enlarge it. Wrap it in clamp() with rem bounds.
What is the accessible pattern for a fluid heading with clamp()?
- A rem floor, a vw-based preferred value, and a rem ceiling
- clamp(5vw, 1rem, 10vw)
- Three vw values
- clamp with only px values
Answer: A rem floor, a vw-based preferred value, and a rem ceiling. Use clamp(rem-floor, vw-growth, rem-ceiling), e.g. clamp(2rem, 5vw, 4rem). The rem bounds keep zoom working while vw drives fluid growth.
In a modular type scale, how is each step's size calculated?
- Add a fixed number of pixels each step
- Halve the previous size
- Multiply the previous size by a chosen ratio
- Use random values
Answer: Multiply the previous size by a chosen ratio. A modular scale picks a base and a ratio (e.g. 1.25), and each step is the previous size times that ratio, giving deliberate, consistent jumps.
What does the 'ch' unit represent, and why use it for line length?
- A character's height; for line-height
- The width of the digit '0' in the current font; to cap the measure
- The viewport's width; for full-bleed text
- A fixed centimetre
Answer: The width of the digit '0' in the current font; to cap the measure. 1ch is roughly one character wide, so max-width: 65ch caps the measure around 65 characters — the comfortable 45-75 character reading range.
Why should line-height usually be unitless (e.g. 1.6)?
- It is shorter to type
- Units are not allowed on line-height
- It disables wrapping
- A unitless value multiplies the font size, so it scales as the font grows
Answer: A unitless value multiplies the font size, so it scales as the font grows. A unitless line-height of 1.6 means 1.6 × the element's font size, so big headings keep proportional spacing — a fixed px line-height won't.
Which text-wrap value is meant for headings to avoid a lonely last word?
- text-wrap: pretty
- text-wrap: balance
- text-wrap: nowrap
- text-wrap: stable
Answer: text-wrap: balance. text-wrap: balance evens out the characters across a heading's lines (best for short text); use text-wrap: pretty for long paragraphs.
What keeps fluid type accessible so browser zoom still enlarges it?
- Using only vw units
- Setting font-size in px
- Every font size containing at least one rem (or em) in its clamp()
- Disabling the viewport meta tag
Answer: Every font size containing at least one rem (or em) in its clamp(). As long as the clamp() floor and ceiling are in rem, zoom and 'larger text' settings still grow the text — a rem in every size keeps it zoom-friendly.