px vs rem vs em — When "Always rem" Is Wrong
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
| Concern | Prefer | Notes |
|---|---|---|
| Body / heading font-size | rem | Scales with root |
| Page spacing scale (margin/gap tokens) | rem | Consistent rhythm |
| Button padding tied to button text | em | Stays proportional if the button font changes |
| 1px borders, outlines | px | Stable hairlines |
| Box-shadow blur tuned visually | px | Fine |
| Media queries | px (common) | Team consistency matters more than purity |
| Icon box next to label | em or rem | em 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”:
- Default font large in OS/browser settings
- Zoom 200%
- Root
font-sizeoverride (some sites sethtml { 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.