CSS offers more than a dozen length units, but in practice most projects rely on four: px, em, rem, and the viewport units (vw/vh and their relatives). Each resolves differently, and choosing the wrong one for a given property is a common source of layouts that look correct in isolation and break the moment a user changes their browser’s font-size setting or resizes the window.
px: The Absolute Baseline
A CSS pixel (px) is a fixed, absolute unit — it does not scale relative to any parent element, font size, or ancestor computation. One px is one px, everywhere in the stylesheet. This makes it the easiest unit to reason about directly, and the right choice for values that genuinely should not scale with text size: border widths, box-shadow offsets, and similar fine visual details.
The tradeoff: because px doesn’t scale, using it for font sizes means text does not respond to a user’s browser-level font-size preference the way relative units do. This matters specifically for accessibility — some low-vision users increase their browser’s default font size rather than relying on page zoom, and a design built entirely in fixed pixels does not respond to that setting the way a design built in relative units does.
em: Relative to the Parent’s Font Size
em is a relative unit — its computed value depends on the font size of the element it’s applied to, which in turn often depends on the font size of its parent. This is the mechanism, and it’s also the source of em’s most notorious bug: compounding.
.container {
font-size: 20px;
}
.container .nested {
font-size: 1.2em; /* 24px (1.2 × 20px) */
}
.container .nested .deeper {
font-size: 1.2em; /* 28.8px (1.2 × 24px), NOT 1.2 × 20px */
}
Each nested level multiplies against its immediate parent’s already-scaled font size, not against the original base. In a deeply nested component (a comment thread, a nested list, a recursive tree component), em-based font sizing can compound unexpectedly, producing text that grows or shrinks far more than intended at each nesting level. This single behavior is the main reason rem exists.
rem: Relative to the Root, Every Time
rem (“root em”) always resolves against the font size of the document’s root element (<html>), regardless of how deeply nested the element using it is. There is no compounding — every rem value in a stylesheet resolves against the same single reference point.
html {
font-size: 16px; /* the rem reference */
}
.container .nested .deeper {
font-size: 1.2rem; /* always 19.2px, at any nesting depth */
}
This predictability is why rem has become the default recommendation for font sizing (and, in many design systems, for spacing values too) in place of em. em still has a legitimate, specific use case: sizing something relative to its own element’s font size, such as padding on a button that should scale proportionally if the button’s own font size changes — that’s a case where em’s parent-relative behavior is exactly the intended behavior, not a bug to avoid.
Why rem (and em) Respect User Font-Size Preferences
Both rem and em ultimately trace back to the root font size, which is typically 16px by default but which the user can change — either through a browser zoom setting or, on some browsers, a dedicated “minimum font size” or default font size preference in accessibility settings. A layout built with rem for font sizes will scale up if a user increases their default font size; a layout built entirely in px will not. This is a real, practical accessibility difference, not a theoretical one — it’s the reason accessibility guidance consistently recommends relative units for text sizing over fixed pixel values.
Viewport Units: vw, vh, vmin, vmax
Viewport units resolve as a percentage of the browser’s viewport dimensions, independent of any element’s font size or box model (see our guide to the CSS box model for how width and sizing otherwise interact):
vw— 1% of the viewport’s widthvh— 1% of the viewport’s heightvmin— 1% of whichever of width or height is smallervmax— 1% of whichever of width or height is larger
.hero {
height: 60vh; /* always 60% of the viewport's height */
}
.headline {
font-size: 5vw; /* scales continuously with viewport width */
}
Viewport units are especially useful for full-bleed hero sections and for continuously fluid typography that scales smoothly across viewport widths rather than jumping between fixed sizes at media query breakpoints. The main caution: font-size: 5vw with no lower or upper bound can produce illegibly small text on narrow phones or absurdly large text on wide desktop monitors, since it scales without limit in either direction.
clamp() Is Newly Available for Bounding Fluid Values
clamp(min, preferred, max) has recently landed across the major browser engines (Chrome, Firefox, and Safari), and it directly solves the unbounded-viewport-unit problem: it takes a minimum value, a preferred (typically viewport-relative) value, and a maximum value, and resolves to whichever keeps the preferred value within the min/max bounds.
.headline {
font-size: clamp(1.5rem, 5vw, 3rem);
/* never smaller than 1.5rem, never larger than 3rem,
scales fluidly with viewport width in between */
}
For projects that need to support somewhat older browser versions, a fallback fixed font-size declared before the clamp() line ensures those browsers still get a reasonable, if non-fluid, value — browsers that don’t recognize clamp() simply ignore that later declaration and keep the earlier fallback.
ch and % : Two More Units Worth Knowing
The ch unit resolves to the width of the “0” (zero) character in the element’s current font — a useful, if approximate, way to size something in terms of readable line length rather than an arbitrary pixel guess:
p {
max-width: 65ch; /* roughly 65 characters per line, a commonly cited readability target */
}
Because ch is defined by the glyph width of a specific character rather than a true average character width, it’s an approximation rather than an exact character count — proportional (non-monospace) fonts will render somewhat more or fewer than the literal number of characters per line — but it remains the most direct native CSS mechanism for reasoning about line length in terms of readability guidance, which is usually expressed in a target character count rather than a pixel width.
Percentage (%) is technically also a length unit in many contexts, though it behaves differently from the others discussed here: a percentage value always resolves relative to some other value — commonly a containing block’s width, but the specific reference differs by property (percentage padding-top, for instance, resolves against the containing block’s width, not its height, as covered in our guide to the CSS box model). Because the reference point varies by property, percentage values require checking the specification for that specific property rather than assuming a single universal rule.
A Practical Default
For most projects: use rem for font sizes and most spacing values (so everything responds consistently to a user’s font-size preference), reach for em specifically when a value should scale with its own element’s font size rather than the document root, use px for details that genuinely should not scale (hairline borders, shadow offsets), and use viewport units — ideally wrapped in clamp() — for values that should respond fluidly to the size of the viewport itself, like hero heights or display typography.
Frequently Asked Questions
Why does my nested component’s font size grow larger than expected?
This is the classic em compounding problem: each nested element with an em font size multiplies against its immediate parent’s already-scaled size, not the original base size. Switching from em to rem for font-size declarations eliminates the compounding, since rem always resolves against the root element’s font size regardless of nesting depth.
Should I ever use px for font sizes?
It’s generally discouraged for body text and most UI text, because px does not respond to a user’s browser-level font-size preference the way rem does — a real accessibility consideration for low-vision users. px remains a reasonable choice for details that shouldn’t scale with text at all, such as border widths or shadow offsets.
What’s the difference between em and rem?
em resolves relative to the font size of its own parent element, and compounds at each nested level if multiple ancestors use em. rem always resolves relative to the root <html> element’s font size, regardless of nesting depth, which avoids the compounding problem entirely. rem is the safer general-purpose default; em is correct specifically when a value should scale with its own element’s font size.
Are viewport units safe to use for font sizing?
They’re powerful but need a safeguard. A bare vw value has no minimum or maximum, so it can shrink to illegible sizes on narrow viewports or grow excessively on very wide ones. Wrapping the viewport-relative value in clamp(min, preferred, max) bounds it at both ends while keeping the fluid scaling in between.
Does changing the browser’s zoom level affect rem the same way as changing font-size preference?
Browser zoom scales the entire page, including images and layout, proportionally — it affects everything, not just rem-based text. A dedicated font-size or text-size accessibility preference (where a browser or OS offers one) more specifically affects the root font size that rem and em are calculated from, without necessarily scaling non-text elements the same way zoom does.



