Px vs Rem vs Em: A Complete Guide to CSS Units (And When to Use Each)
Every CSS length eventually gets typed as one of three things: a pixel, a rem, or an em. They can all produce the same visual size on screen, which is exactly why the difference is easy to ignore — right up until a user bumps their browser font-size, a component gets nested somewhere unexpected, or a design review flags text that "looks a little small." Here's what each unit actually measures, the arithmetic behind converting between them, and a concrete rule for picking one over the others.
The short version
| Unit | Relative to | Default value | Compounds? |
|---|---|---|---|
px | Nothing — a fixed CSS pixel | 1px = 1px | No |
rem | The root (<html>) font-size | 1rem = 16px | No |
em | The current element's own font-size | 1em = parent's computed size | Yes, when nested |
Px: the pixel that isn't quite a pixel
A CSS pixel isn't a literal screen pixel — on a high-DPI display it maps to several physical pixels — but
for layout purposes it behaves as a fixed, absolute unit. 16px is 16px whether it
sits on a heading, a button, or a footer link. That predictability is also its weakness: a font-size set in
px does not respond when a user increases their browser's default text size in accessibility settings.
WCAG's 1.4.4 Resize Text criterion expects text to scale up to 200% without breaking
layout or losing content, and text locked to px units can fight that.
Rem: relative to one number, everywhere
A rem ("root em") is always relative to the <html> element's font-size, which defaults
to 16px in every major browser unless a user or a stylesheet changes it. That means
1.5rem is 24px no matter how deeply nested the element is — the value never
compounds through parent elements the way em can. This is why rem became the default recommendation for
font-size, and often for padding and margin too: it scales cleanly with a user's accessibility settings,
and a single change to the root font-size (rare, but sometimes used for a "compact mode" toggle) rescales
the entire page proportionally.
Converting px to rem
The formula is just division by the root font-size (16, unless you've changed it):
rem = px / 16
| px | rem (at 16px root) |
|---|---|
| 12px | 0.75rem |
| 14px | 0.875rem |
| 16px | 1rem |
| 18px | 1.125rem |
| 20px | 1.25rem |
| 24px | 1.5rem |
| 32px | 2rem |
| 40px | 2.5rem |
| 48px | 3rem |
| 64px | 4rem |
If a design file hands you a raw pixel value, the px, rem & em converter does this instantly and also shows the em equivalent for a given parent size, so you don't have to keep a cheat sheet open in another tab.
The old 62.5% trick
A common shortcut sets html { font-size: 62.5%; } so that 1rem becomes
10px, making mental math easier (1.6rem = 16px instead of awkward decimals). It
still works and doesn't break user zoom or the resize-text accessibility criterion, since it's just a
different relative baseline — but it does mean anyone reading your CSS has to remember the root isn't
16px, which trips up new team members and third-party component libraries that assume the browser default.
Most teams now skip the trick and just write the rem value directly, letting a build-time tool or a quick
mental "divide by 16" handle the conversion.
Em: relative to its own element, which is why it compounds
An em is relative to the font-size of the element it's declared on — and if that property isn't set explicitly, the browser resolves it against the inherited (parent) font-size. That inheritance is what causes the classic nested-list bug:
li { padding-left: 1.5em; }
On a flat list this pads every item by 1.5× its font-size. But nest a <ul> inside a
<li>, and the child list's 1.5em padding is now 1.5× its own
inherited font-size — and if any ancestor also declared a relative font-size, the effective indentation
compounds with every level of nesting, producing a staircase effect nobody designed on purpose.
Where em is actually the right choice
Compounding isn't a bug when you want it — it's how you make a component's spacing scale with its own type size. A button built with:
.btn {
font-size: 1rem;
padding: 0.5em 1em;
}
.btn--large { font-size: 1.25rem; }
automatically gets larger padding on the .btn--large variant, because the padding is defined
relative to whatever font-size that specific button ends up with — no second padding rule needed. The same
pattern works well for icon sizes next to text, badge padding, and anything meant to grow or shrink in
lockstep with a local font-size change rather than the whole page's root size.
A working rule of thumb
- Font-size (body text, headings): rem — respects the user's browser/accessibility settings and never compounds unexpectedly.
- Page-level spacing (section padding, grid gaps, max-width): rem, for the same reason.
- Component-local spacing that should scale with the component's own font-size (button padding, badge padding, icon sizing): em.
- Borders, box-shadows, and other hairline details: px — a 1px border should stay 1px, not become a blurry 1.0625px when zoomed oddly.
- Media query breakpoints: either em or rem is fine in modern browsers; a long-standing Safari zoom quirk once made em the safer choice, but current engines handle both consistently.
How a fixed-px assumption turns into a real bug
The failure mode isn't hypothetical. A user who sets their browser's default font size to 20px instead
of 16px (common among users with mild low vision, and a completely supported browser preference, not an
edge case) expects every rem-based value on the page to scale up proportionally along with it
— a 1.5rem heading becomes 30px instead of 24px, a 1rem paragraph becomes 20px
instead of 16px, and the whole page's rhythm stays consistent. A page built with px font-sizes
ignores that preference entirely: the text a user explicitly asked to be larger stays exactly the same
size, because px was never relative to anything the browser setting could influence. This is the concrete,
everyday version of the WCAG 1.4.4 resize-text requirement — it isn't only about pinch-zooming on mobile,
it's about a standing browser preference that a meaningful number of real visitors already have set.
Rem doesn't always mean 16px — check the actual root
It's easy to internalize "1rem = 16px" as a fact rather than a default. Any of the following changes what
a rem actually resolves to on a given page: a project deliberately setting html { font-size: 62.5%; }
(making 1rem = 10px), a user's own browser default font-size setting, or a browser's page zoom
combined with a non-default OS text-size setting. Before assuming a rem value in someone else's stylesheet
means what you expect, it's worth a quick check of the actual computed <html> font-size
in devtools — the number is sometimes not 16.
A mixed-unit layout in practice
Real stylesheets usually combine all three units deliberately rather than picking just one globally: rem for type and page-level spacing, em for component-local padding that should track a component's own font-size, and px for the handful of things that genuinely shouldn't scale — a 1px hairline border, a 2px focus outline offset, a small fixed-size decorative icon. Treating the choice as "which unit is right for this specific property," rather than "which unit does this whole project use," produces fewer surprises than a blanket rule applied everywhere without exception.
Frequently Asked Questions
Does changing the root font-size break fixed-px layouts elsewhere?
No — anything still declared in px is unaffected by a root font-size change, which is exactly why mixing rem-based type with px-based layout containers can look inconsistent if a user zooms text but not the overall page.
What's 1.5rem in pixels?
At the default 16px root, 1.5rem = 24px. If a project has changed the root font-size (via the 62.5% trick or otherwise), multiply 1.5 by whatever that root value actually is.
Should I ever use px for font-size?
Generally no for body copy and headings — use rem so text scales with user preferences. Px is still reasonable for things that aren't really "text" in the accessibility sense, like a fixed-size icon glyph.
Is em deprecated in favor of rem?
No, they solve different problems. Rem gives you one predictable page-wide scale; em gives you component-local scaling that compounds through the element tree. Reach for whichever behavior you want.
Do rem and em affect layout performance?
No — both are resolved to an actual pixel value at layout time just like px, with no runtime cost beyond that single calculation. The choice is purely about how the value behaves under zoom, nesting, and user preference, not about speed.
What unit should line-height use?
A unitless line-height (e.g. line-height: 1.5;) is usually the better default over any of
the three — it scales as a ratio of whatever font-size is in effect on that element, including through
nested elements with different sizes, without inheriting a fixed computed value the way a unit-based
line-height would.