Font sizes in CSS look like a matter of taste: px because it is predictable, rem because someone said so, clamp() because it is modern. The choice affects real readers, though, especially those who change their default font size or zoom the page. This article explains what each unit does, derives a clamp() value by hand (and checks it with our CSS unit converter), and shows with numbers how fluid type interacts with zoom.
px, rem and em
- px is a CSS pixel, a fixed unit (1in = 96px). 16px is 16px whatever the reader's settings.
- rem is the root element's font size. Browsers set it to 16px by default, but a reader can change that default (many people with low vision do), and then every rem size follows.
- em is the parent's font size (for font-size). Because it compounds, a list inside a list at 0.9em gets 0.81 of the outer size.
Browser zoom enlarges everything, px included. The default font size setting only changes rem and em. A page whose text is in px therefore ignores that setting. That is the main reason to size text in rem: 1.125rem is 18px for most readers and 22.5px for a reader who chose 20px.
The converter shows any length in every unit: 24px is 1.5rem at a 16px root, 18pt, and 1.6667vw on a 1440px-wide window.
Deriving clamp() by hand
Say body text should be 16px on a 320px-wide phone and 24px on a 1280px laptop, growing smoothly in between and staying put outside that range. A straight line through two points:
- Slope = (24 − 16) / (1280 − 320) = 8 / 960 = 0.008333 px per px of width. As a vw value (1vw = 1% of the width) that is 0.8333vw.
- Intercept = 16 − 0.008333 × 320 = 13.333px, which is 0.8333rem at a 16px root.
- Result: font-size: clamp(1rem, 0.8333rem + 0.8333vw, 1.5rem);
Check at 800px: 13.333 + 0.008333 × 800 = 20px, exactly halfway, as a straight line should be. The tool gives the same value, the formula, and a table of sizes at common widths.
Writing the intercept in rem rather than px matters: the rem part follows the reader's font-size setting, the vw part does not.
What zoom does to the vw part
Zooming to 200% does not change the physical window, but it halves the width in CSS pixels. A 1280px window at 200% zoom is a 640px viewport to the page. Take the clamp() above on a 1280px-wide window:
| Zoom | Viewport in CSS px | Computed size | On screen | Relative to 100% |
|---|---|---|---|---|
| 100% | 1280 | 24px | 24px | 1× |
| 200% | 640 | 18.67px | 37.3px | 1.56× |
| 300% | 427 | 16.89px | 50.7px | 2.11× |
| 400% | 320 | 16px | 64px | 2.67× |
The text grows, but more slowly than the zoom, because the vw part shrinks as the CSS viewport narrows. Here it reaches double size at 300% zoom. Compare pure viewport units, font-size: 2vw: at 1280px that is 25.6px, and at 200% zoom it is 12.8 CSS px, displayed at 25.6px. It never grows. That is why text sized only in vw fails WCAG 1.4.4 (resize text to 200%).
Two habits keep fluid type usable:
- Always keep a rem part in the preferred value, as the derivation above does.
- Keep the range modest. A rule of thumb from published analyses of fluid type is that the largest size should be no more than 2.5 times the smallest, so text can still reach twice its size within the browser's zoom range. The tool flags pairs beyond that. Then test with real zoom; rules of thumb are not a substitute.
Type scales
Headings are where fluid sizing pays off: an h1 can be 32px on a phone and 56px on a desktop while body text barely changes. A modular scale multiplies a base by a ratio per step. With 16px and a major third (1.25): 12.8, 16, 20, 25, 31.25, 39.06px. A fluid scale uses one base and ratio at the small width and another at the large width, then turns each step into its own clamp(). With base 16px and ratio 1.25 at 320px, and 18px and 1.333 at 1280px, step 0 is clamp(1rem, 0.9583rem + 0.2083vw, 1.125rem) and the larger steps grow more. The tool writes these as CSS variables (--step-0, --step-1…) or as a Tailwind theme.
Common mistakes
- Text in px, which ignores the reader's default font-size setting.
- Pure vw font sizes, which do not grow with zoom.
- Huge fluid ranges, such as 16px to 64px on the same element: zooming cannot keep up.
- Setting the root font size in px (html { font-size: 10px }) to make 1rem = 10px. That throws away the reader's setting for the whole page. If you want that arithmetic, 62.5% does the same while staying proportional to their choice.
- Mixing up em and rem in nested components and getting compounded sizes.
- Media-query jumps from 16px to 24px at one breakpoint: clamp() gives a smooth ramp instead.
For layout values that pair with the type scale, the grid and flexbox generator writes the rest of the CSS.