px vs rem vs em — When "Always rem" Is Wrong

(Updated: July 16, 2026 ) CSS units a11y px-rem

Thread consensus says “never use px; always rem.” Real stylesheets that ship products use all three — px, rem, and em — because they solve different problems. Blind rules create weird borders, unreadable components, or spacing that fights the type size inside a widget.

Quick definitions

  • px — CSS pixels (device-adjusted). Predictable for hairlines, many breakpoint systems, and shadows tuned by eye.
  • rem — Relative to the root (html) font-size. Ideal for type ramps and page-level spacing scales that should respect user root font settings.
  • em — Relative to the element’s (or inherited) font-size. Ideal when padding, margin, or icon size should grow with that component’s text.

If you remember only one sentence: rem follows the page root, em follows the local font, px stays a fixed hairline when you need chrome precision.

A practical assignment matrix

ConcernPreferNotes
Body / heading font-sizeremScales with root
Page spacing scale (margin/gap tokens)remConsistent rhythm
Button padding tied to button textemStays proportional if the button font changes
1px borders, outlinespxStable hairlines
Box-shadow blur tuned visuallypxFine
Media queriespx (common)Team consistency matters more than purity
Icon box next to labelem or remem if icon should track local type

Where “always rem” goes wrong

Borders becoming fractional — Root font tweaks can make “1rem÷16” thinking produce subpixel borders that look uneven across sides. Keep hairlines in px.

Components that should be self-contained — A card with font-size: 0.875rem and padding in rem keyed only to the page root will not tighten when you shrink only the card’s type. Padding in em keeps the chip coherent.

Third-party widgets — Some embeds assume px. Wrapping rem overrides can explode layout. Scope carefully and isolate resets.

Design-token confusion — Figma is labeled in px. Converting every mock value with a ÷16 assumption fails when your root is 62.5% or 10px. Converting designs with px-rem helps when the mock is labeled in px but your tokens are rem — set the real root first.

User preferences and zoom

Modern browser zoom generally scales px and rem together for the layout viewport. The accessibility win of rem is stronger when users set a larger default font size — rem-based text and spacing follow; a purely px type system may stay visually smaller relative to that preference. Design tokens in rem make respecting that preference easier.

Still test three things before calling a type system “done”:

  1. Default font large in OS/browser settings
  2. Zoom 200%
  3. Root font-size override (some sites set html { font-size: 62.5% } — know your project’s convention)

Root font tricks and mixed-unit examples

html { font-size: 62.5%; } makes 1.6rem ≈ 16px when the default is 16px — convenient math, confusing for newcomers. Document it in the design-token README so converters and new hires share one assumption.

:root {
  --text-body: 1rem;
  --space-4: 1rem;
}

.button {
  font-size: 0.875rem;
  padding: 0.75em 1.25em; /* tracks button text */
  border: 1px solid #ccc;  /* hairline */
  border-radius: 0.5rem;   /* tied to root scale */
  margin-block: var(--space-4);
}

This mix is not hypocrisy. It is assigning each responsibility to the unit that matches it.

Decision heuristic for code review

Ask: should this scale with the page root, the local font, or stay a fixed hairline? Answer rem, em, or px. Ignore slogans that ban a unit you need for a crisp 1px separator. Write the choice down in the design-token doc so review does not restart the holy war every sprint.

Also agree on media-query units as a team. Switching half the breakpoints to rem mid-project creates more bugs than it prevents. Consistency beats purity.

Units are tools. Use rem for the type and spacing system, em for component-local proportion, px for precision chrome — and stop treating any one of them as a moral failing.