Type something to search...
Understanding CSS font-size Units: px, em, rem, vw, and ch

Understanding CSS font-size Units: px, em, rem, vw, and ch

You set a nested list's font size to 0.9em so it looks slightly smaller than body text, and three levels deep the items have shrunk to a barely readable size. Elsewhere, a hero headline sized in vw looks great on your monitor but refuses to grow when a user zooms in, and a product card with max-width: 60ch comes out narrower than you expected in one font and wider in another. Each of these bugs comes from the same place: not knowing exactly what a CSS unit is measured against.

This article explains every unit you are likely to use for typography and type-related layout: px, em, rem, %, vw and its viewport cousins, ch, ex, cap, lh, and container query units. For each one, you will see what it resolves against, how it responds to zoom and user font settings, and where it belongs in a real stylesheet.

How CSS Units Are Grouped

CSS lengths fall into three families:

  1. Absolute units have a fixed size: px, pt, cm, in. On screens, only px matters in practice.
  2. Font-relative units depend on a font's size or metrics: em, rem, ex, ch, cap, ic, lh, rlh.
  3. Viewport and container units depend on the size of the viewport or a container: vw, vh, vi, vb, vmin, vmax, the sv*, lv*, and dv* variants, and cqw, cqi, and friends.

Percentages are a fourth case. For font-size, a percentage is relative to the parent's font size, exactly like em.

The Quick Reference

UnitRelative toFollows user's default font size?Scales with browser zoom?Best use
pxA CSS reference pixel (1/96 inch)NoYesBorders, hairlines, small fixed details
remRoot element's font sizeYesYesFont sizes, global spacing
emThe element's own font size (parent's, for font-size)YesYesSpacing that should scale with local text
%Parent font size (for font-size)YesYesRarely needed; equivalent to em
chAdvance width of the "0" glyphYesYesLine length, input widths
exx-height of the fontYesYesFine vertical alignment
capCap height of the fontYesYesAligning icons with capitals
lhElement's computed line heightYesYesSpacing on the text rhythm, clamped heights
rlhRoot element's line heightYesYesGlobal vertical rhythm
vw1% of viewport widthNoInconsistentOnly inside clamp() with a rem term
cqi1% of the container's inline sizeNoNoComponent-level fluid type, with rem

The two columns about user settings are the ones that matter most for accessibility. Browser zoom scales everything measured in CSS pixels. The default font size setting (Settings, Appearance, Font size in Chrome, for example) only changes the root font size, which means only units derived from it respond. People with low vision often rely on that setting, so font sizes that ignore it can fail them.

px: The CSS Pixel

A CSS px is not a physical device pixel. It is a reference pixel, defined as 1/96 of an inch at a typical viewing distance. On a high-density phone display, one CSS pixel may be three or four device pixels. That abstraction is why a 16px font looks roughly the same size across devices.

px scales with browser zoom, because zoom changes how many device pixels a CSS pixel uses. It does not respond to the user's default font size. If someone sets their browser default to 20px and your body text is font-size: 16px, they still get 16px.

Use px for things that should not scale with text: 1px borders, focus ring offsets, shadow blur radii, and small decorative details. The full argument for avoiding px on font sizes is in why you should use rem instead of px for font sizes.

rem: Root Em

A rem equals the computed font size of the root html element. If you never set a root font size, that is the browser default, normally 16px, or whatever the user changed it to.

/* Do not set a px root size; leave the user's default intact */
html {
  font-size: 100%;
}

body {
  font-size: 1rem; /* 16px by default, 20px for a user who chose 20px */
}

h1 {
  font-size: 2.5rem; /* 40px by default */
}

.card {
  padding: 1.5rem; /* spacing scales with text size too */
}

rem is the right default for font sizes because it is predictable (no compounding) and respects user settings. It is also a good unit for global spacing, so whitespace grows proportionally when text grows.

The 62.5% Trick

Some codebases set html { font-size: 62.5% } so that 1rem equals 10px and math is easier. It still respects user settings, since the percentage is applied to the user's default. The cost is that every component must remember to set body text back to 1.6rem, and third-party widgets that assume 1rem is body size render tiny. Most teams are better off keeping the default and thinking in rem directly: 0.875rem is 14px, 1.125rem is 18px, 1.5rem is 24px.

em: Relative to the Current Font Size

An em is the computed font size of the current element. There is one special case: when used in the font-size property itself, em refers to the parent's font size, because the element's own size is what you are calculating.

That difference produces the compounding problem:

li {
  font-size: 0.9em;
}
<ul>
  <li>
    Level 1 (0.9 × 16 = 14.4px)
    <ul>
      <li>
        Level 2 (0.9 × 14.4 = 12.96px)
        <ul>
          <li>Level 3 (0.9 × 12.96 = 11.66px)</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

Each nesting level multiplies again. Switch to rem and every list item is exactly 0.9 × root.

Where em Is the Right Choice

Compounding is a bug for font sizes and a feature for spacing that should scale with local text. Padding on a button, the gap between an icon and its label, letter-spacing, and margins around inline code all look best when they track the element's own size:

.button {
  font-size: 1rem;
  padding: 0.6em 1.2em; /* grows automatically for .button-lg */
  gap: 0.5em;
}

.button-lg {
  font-size: 1.25rem;
}

.eyebrow {
  letter-spacing: 0.08em; /* tracking should always be in em */
}

letter-spacing and word-spacing should essentially always be in em. WCAG 1.4.12 itself expresses its text spacing thresholds this way: letter spacing of 0.12em and word spacing of 0.16em.

ch: The Width of Zero

A ch is the advance width of the "0" (zero) glyph in the element's font. It is the natural unit for line length, because readable body text sits in a range of about 45–75 characters per line.

.prose {
  max-width: 65ch;
}

input[name="postcode"] {
  width: 8ch;
}

The catch: ch measures the zero, not an average character. In most proportional fonts, the zero is wider than an average lowercase letter, so 65ch usually fits somewhat more than 65 characters of real text. In a monospace font, ch is exact. Narrow fonts and wide fonts also produce different results for the same ch value. Treat it as a good approximation and check the actual count. The article on the ideal line length for web content explains how to tune it.

Font-Metric Units: ex, cap, ic, lh, and rlh

These units are tied to specific font metrics, which makes them useful for precise alignment.

  • ex equals the font's x-height, the height of lowercase letters like x.
  • cap equals the cap height, the height of capital letters.
  • ic equals the advance of the CJK water ideograph, useful for Chinese, Japanese, and Korean layouts.
/* Size an inline icon to match capital letters */
.icon-inline {
  height: 1cap;
  width: auto;
  vertical-align: baseline;
}

/* Raise a superscript-like badge by half the x-height */
.badge-sup {
  position: relative;
  top: -0.5ex;
}

cap is supported in all current major browsers and is far better than guessing 0.7em for icons next to text. These metrics are defined by the typeface designer, so the same 1cap differs from font to font.

lh and rlh: Line-Height Units

lh equals the computed line-height of the element, and rlh equals the root element's line height. They make vertical spacing that matches your text rhythm easy:

body {
  line-height: 1.6;
}

p + p {
  margin-block-start: 1lh; /* exactly one line of space */
}

.excerpt {
  min-height: 3lh; /* reserve three lines */
}

hr {
  margin-block: 2rlh;
}

Both are supported in current versions of Chrome, Edge, Firefox, and Safari.

vw, Viewport, and Container Units

A vw is 1% of the viewport width. Sizing text in pure vw makes it scale with the window, which seems attractive for headlines:

/* Avoid: ignores user font settings and behaves poorly under zoom */
h1 {
  font-size: 6vw;
}

There are two problems. First, vw ignores the user's default font size. Second, when a user zooms, the viewport's CSS width shrinks, so text in vw can stay the same size or even get smaller. That can fail WCAG 1.4.4, which requires text to be resizable to 200% without loss of content or function.

The fix is to always combine viewport units with rem and cap the result:

h1 {
  font-size: clamp(2rem, 1.25rem + 3vw, 4rem);
}

The rem term keeps zoom and user settings working, and the clamp() bounds keep the size sensible. This is the core of fluid typography with CSS clamp().

Small, Large, and Dynamic Viewport Units

On mobile, browser toolbars appear and disappear, which changes the viewport height. CSS now provides svh (small viewport, toolbars shown), lvh (large viewport, toolbars hidden), and dvh (dynamic, updates as toolbars move), along with matching width and logical variants. These matter for full-height hero sections, not for font sizes. Do not size text in any vh variant.

Logical Viewport Units

vi and vb refer to the inline and block axes. In horizontal writing modes, vi equals vw. In vertical writing modes, used in some Japanese and Chinese layouts, it follows the text direction instead. Prefer vi if you support vertical text.

Container Query Units

Container units such as cqi (1% of the container's inline size) let type scale with the component's width rather than the viewport. A card in a narrow sidebar and the same card in a wide main column can size their titles independently:

.card-wrapper {
  container-type: inline-size;
}

.card__title {
  font-size: clamp(1.125rem, 0.9rem + 2cqi, 1.75rem);
}

Like vw, container units ignore user font settings, so keep a rem term in the calculation. The article on container queries for responsive typography covers the full pattern.

Keywords and Percentages

font-size also accepts keywords: absolute ones (xx-small through xxx-large, with medium as the user's default) and relative ones (smaller, larger). They respect user settings but give you little control, so they are rarely used in design systems.

Percentages behave like em for font size: font-size: 120% equals 1.2em and compounds the same way. For line-height, avoid percentages and em; use a unitless number such as 1.5, which is inherited as a ratio instead of a fixed computed length.

/* Bad: children inherit a fixed 24px line height */
body {
  font-size: 1rem;
  line-height: 150%;
}

/* Good: children inherit the 1.5 ratio and apply it to their own size */
body {
  font-size: 1rem;
  line-height: 1.5;
}

Which Unit Should You Use?

Property or taskRecommended unit
Body and heading font-sizerem, or clamp() combining rem with vw or cqi
line-heightUnitless number
letter-spacing, word-spacingem
Component padding tied to textem
Layout spacing and gapsrem
Measure (max-width of text)ch, or rem if you need exact pixel targets
Vertical rhythmlh, rlh, or rem
Icon size beside textcap or em
Borders and hairlinespx
Media query breakpointsem (computed against the browser default)

That last row deserves a note. In media queries, em and rem both refer to the browser's initial font size, not your root style, and em breakpoints respond consistently when users change their default font size. Use @media (min-width: 48em) rather than 768px if you want layouts to adapt when people enlarge text.

Testing Your Units

  1. Change the browser default font size to 20px or 24px and reload. Text and spacing in rem and em should grow; anything that stays the same is in px or vw.
  2. Zoom to 200%. Content should reflow without clipping or overlap, as WCAG 1.4.4 requires.
  3. Inspect computed values. DevTools' Computed panel shows the final pixel value of any font size, which makes compounding easy to spot.
  4. Swap the font. If a layout depends heavily on ch or ex, test with your fallback font to make sure it still works before the web font loads.

CSS Units FAQ

A rem is always relative to the root element's font size, so it is the same everywhere on the page. An em is relative to the current element's font size, or the parent's when used for font size itself, so nested em values multiply. Use rem for font sizes and em for spacing that should follow local text size.

It is not broken, because browser zoom still enlarges px text. The problem is that px ignores the user's default font size setting, which many low-vision users rely on. Using rem respects both zoom and that setting at no cost.

The ch unit measures the width of the zero glyph, which is usually wider than an average letter in a proportional font. Real text therefore fits a few more characters than the ch value. In monospace fonts the measurement is exact.

Only in combination with rem, for example inside clamp. Pure viewport units ignore the user's font size setting and may not grow when the page is zoomed, which can fail WCAG resize text requirements.

A unitless number such as 1.5. It is inherited as a ratio, so each element computes its own line height from its own font size. Percentages and em values are inherited as fixed lengths, which causes cramped or overly loose lines in children with different sizes.

Yes. Both are supported in current versions of Chrome, Edge, Firefox, and Safari. Provide a simple fallback in em or rem only if you must support much older browsers.

Conclusion

Every CSS unit answers one question: relative to what? px is relative to a fixed reference pixel, rem to the root font size, em to the current element, ch and cap to the font's own glyphs, lh to the line height, and vw and cqi to the viewport or container. Once you know the reference, the bugs in the opening become obvious: em compounding in nested lists, pure vw text ignoring zoom, and ch measuring a zero rather than an average letter.

For most projects, a short rule set covers nearly everything. Size type in rem, made fluid with clamp() where needed. Use unitless line heights, em for tracking and text-relative padding, ch for measure, and px only for hairlines. Then test with a larger default font size and 200% zoom, and you will know your typography respects the people reading it.

Here are some useful references for going deeper on CSS units:

  1. MDN Web Docs: CSS values and units — an overview of absolute, relative, and viewport units.
  2. MDN Web Docs: length — the complete list of length units, including cap, lh, rlh, and container units.
  3. W3C: CSS Values and Units Module Level 4 — the specification that defines each unit.
  4. W3C WAI: Understanding SC 1.4.4 Resize Text — why text must scale to 200%.
  5. web.dev: Learn CSS: Sizing units — practical guidance on choosing units.
Share :

Related Posts

Ascenders, Descenders, and Baselines: The Anatomy of a Letterform

Ascenders, Descenders, and Baselines: The Anatomy of a Letterform

You align an icon next to a button label and it looks a pixel or two too high, no matter how you adjust vertical-align. You set overflow: hidden

Continue Reading
Are Google Fonts GDPR-Compliant?

Are Google Fonts GDPR-Compliant?

In late 2022, thousands of small business owners in Germany and Austria opened letters demanding a few hundred euros in "damages" because their websi

Continue Reading
How to Audit Web Font Performance with Lighthouse?

How to Audit Web Font Performance with Lighthouse?

A client sends you a screenshot of their PageSpeed Insights report: performance score 61, LCP 3.9 seconds, and a vague list of warnings. They want to

Continue Reading