Absolute vs relative — and why it matters for accessibility
The core split: absolute units (px) are fixed, while relative units (rem, em, %, vw) depend on something else. For font sizes this isn’t just preference — it’s accessibility. Users can change their browser’s default font size to cope with low vision, and px font sizes ignore that preference, while rem scales with it. WCAG 2.1 (1.4.4) requires text to resize to 200% without breaking, which rem-based sizing helps satisfy. The default base is 16px, so 1rem = 16px unless the root font-size is overridden.
Pick the unit by what you’re sizing
There’s no single “best” unit — match it to the job:
| Property | Reach for | Why |
|---|---|---|
| Font size | rem | Respects user zoom preference |
| Component padding tied to its text | em | Scales with the element’s own font size |
| Layout width/height | %, fr, px | Relative to parent or fixed |
| Full-viewport sections | dvh/svh | Mobile-chrome-safe (not bare vh) |
| Fluid type | clamp() w/ vw | Continuous scaling |
| Context-aware components | cqi/cqw | Relative to the container |
The em-compounding trap
em is relative to the parent’s font size, so it compounds when nested: a 1.2em element inside another 1.2em element renders at 1.44× the base. That’s powerful for components that should scale as a unit, but it’s a frequent source of “why is this text huge three levels deep?” bugs. When in doubt for font sizing, rem (always relative to the root) avoids the surprise; reserve em for padding/margins you want to track the element’s own text size.
The mobile vh fix
100vh on mobile is taller than the visible area because it includes the space behind the browser’s address bar, causing content to be cut off or jump as the bar hides. Use 100dvh (dynamic viewport height, adjusts as chrome appears/disappears) or 100svh (small viewport height, the conservative choice) instead. These are supported in current Chrome, Safari, and Firefox and fix the classic “full-height section is slightly too tall on mobile” problem.