Design
px vs em vs rem in CSS: Which Unit to Use and When
px is an absolute pixel, em is relative to the element's own font-size, rem is relative to the root. Default to rem for accessibility — here is why and when.
Three CSS length units do almost the same job, and choosing wrong quietly breaks accessibility for the people who need it most. px, em, and rem all set sizes, but they answer to different masters: a fixed device-independent pixel, the current element's font-size, and the root element's font-size. Pick px for a heading and a user who bumps their browser font to 24px gets nothing. Pick em for nested padding and your buttons balloon two levels deep. This guide draws the lines exactly, with the spec rules and the numbers.
TL;DR: what is the difference between px, em, and rem?
px is an absolute length — one CSS pixel, fixed regardless of font settings (CSS Values 4). em is relative to the element's own font-size — so it compounds when elements nest, because each child's em is measured against its parent's computed font-size. rem ("root em") is relative to the root <html> element's font-size — one fixed reference, so it never compounds (CSS Values 4).
The one-paragraph decision rule:
- Use
remforfont-size, margins, padding, and layout spacing — it respects the user's browser font-size setting, which is the whole accessibility argument. - Use
pxfor things that should not scale:1pxborders and hairlines, fine box-shadow offsets, and (debatably) media-query breakpoints. - Use
emfor spacing that should track the local font-size — padding inside a button, the gap after a heading, an icon sized to the text beside it.
Everything below is the detail. To convert a px value to rem (or back) instantly, use the unit converter, or the dedicated px to rem converter and rem to px converter.
What does px actually mean in CSS?
A CSS px is not one hardware pixel. It is an absolute length defined so that 1px is 1/96 of an inch, anchored to a reference viewing distance (CSS Values 4 §6.2). On a high-density (Retina) display, one CSS pixel spans several physical pixels — the browser handles the mapping through devicePixelRatio. That is why a 1px border looks crisp on a phone instead of microscopic.
The key property for our comparison: px is fixed. It does not respond to the element's font-size, the root font-size, or the user's preferred text size. A font-size: 16px heading is 16px whether the user left their browser default alone or cranked it to 200% for low vision. That immovability is the source of both its best use and its worst.
Why rem is the accessibility default
Most browsers ship a default root font-size of 16px, and users can change it. When someone sets their browser's default text size to "Large" (often 20px) or "Very large" (24px), every length expressed in rem scales with it, because rem is measured against that root font-size. Lengths expressed in px ignore the setting entirely.
This is not a theoretical preference. WCAG 2.2 Success Criterion 1.4.4 (Resize Text) requires that text can be resized up to 200% without loss of content or function. A layout built in px font-sizes fails the spirit of this for the large population who rely on the browser's own zoom-by-font-size feature rather than full-page zoom.
Here is the contrast in code:
/* The user set their browser default font-size to 20px. */
/* IGNORES the user — stays 16px no matter what. */
.title-fixed {
font-size: 16px;
}
/* RESPECTS the user — root is now 20px, so this renders at 20px. */
.title-fluid {
font-size: 1rem;
}
/* And a heading scaled off the root: 1.5 × 20px = 30px. */
.heading {
font-size: 1.5rem;
}
The critical rule: never hard-set html { font-size: 16px } or, worse, a percentage that pins it. Leave the root font-size to inherit the browser default (or set it as a percentage like 100% so the user's value still flows through). The moment you write html { font-size: 16px }, you have re-broken the thing rem was supposed to fix.
The em compounding trap
em is relative to the computed font-size of the element it is used on. For most properties that is convenient. For font-size itself it is a trap, because nested elements multiply.
Walk through a list nested three deep, each level setting font-size: 1.25em, starting from a 16px root:
html { font-size: 16px; }
li { font-size: 1.25em; } /* relative to the PARENT's font-size */
Root font-size: 16px
Level 1 <li>: 16 × 1.25 = 20px
Level 2 <li>: 20 × 1.25 = 25px ← not 20px
Level 3 <li>: 25 × 1.25 = 31.25px ← not 20px
Level 4 <li>: 31.25 × 1.25 = 39.06px ← runaway
Each level inherits the previous level's already-multiplied size and multiplies again. Three levels deep, your list items are nearly double the intended size — a bug that only appears on deeply nested content, so it ships to production constantly. Swap that one rule to rem and every level is a flat 1.25 × 16 = 20px, because rem always measures against the root, not the parent:
li { font-size: 1.25rem; } /* every level = 20px, no compounding */
This is the single clearest reason to prefer rem for font-size: it removes a class of bug that em invites.
When em is the right tool
em compounding is a feature, not just a bug, when you want a value to track the local font-size. The textbook case is padding inside a component:
.button {
font-size: 1rem; /* fixed reference for THIS button */
padding: 0.75em 1.5em; /* padding scales WITH the button's text */
}
.button--large {
font-size: 1.25rem; /* bump only the font-size... */
/* ...and the padding grows proportionally, no second rule needed */
}
Because the padding is in em, it is measured against the button's own font-size. Change the font-size once and the button stays perfectly proportioned at every size. Write that padding in rem or px and you would need a separate padding rule for each button size.
The same logic applies to the gap after a heading (margin-bottom: 0.5em keeps the space proportional to the heading), icon sizing (width: 1em makes an icon match its adjacent text), and any "this should grow with its text" relationship. Reach for em when the answer to "relative to what?" is "the text right here."
px vs em vs rem: the comparison table
| Unit | Relative to | Compounds when nested? | Best for |
|------|-------------|------------------------|----------|
| px | Nothing — absolute (1/96in) | No | 1px borders, hairlines, fine shadow offsets, breakpoints (debated) |
| em | The element's own computed font-size | Yes — multiplies down the tree | Component-local spacing, button padding, icon sizing, space after a heading |
| rem | The root <html> font-size (≈16px) | No | font-size, layout margins/padding, widths — the accessible default |
The math: converting px to rem and back
Because rem is measured against the root font-size, the conversion is plain division:
rem = px ÷ root-font-size
px = rem × root-font-size
With the 16px default root:
24px ÷ 16 = 1.5rem
18px ÷ 16 = 1.125rem
1rem × 16 = 16px
0.875rem × 16 = 14px
A common trick is setting html { font-size: 62.5% } so the root becomes 10px and the math is px ÷ 10 (so 2.4rem = 24px). It is tempting, but it overrides the browser default and re-introduces the accessibility problem unless you also re-scale your body text back up — most teams skip the trick and just do the division. Run any value through the px to rem converter or the reverse rem to px converter to skip the arithmetic, or the multi-unit unit converter for everything at once.
When px is still the right choice
rem is the default, not a dogma. Three cases where px is correct:
- Borders and hairlines. A
1pxborder should be1pxat every font-size.border: 1px solidis right;border: 0.0625rem solidis pedantry that rounds to a fuzzy line on some displays. - Fine shadow and inset offsets. A
box-shadow: 0 1px 2pxor a2pxfocus-ring offset is a crisp visual detail, not body text — it should not balloon when the user enlarges their font. Build shadows with the box-shadow generator and corner radii with the border-radius generator; small fixedpxvalues are fine there. - Media-query breakpoints (debated). Many engineers use
pxbreakpoints because they map to device classes. Others useembreakpoints so the layout reflows when a user enlarges text — note media queries always measureem/remagainst the browser default (16px), never yourhtmloverride. Both are defensible; pick one and be consistent.
Decorative work like CSS gradients is unit-agnostic, but anywhere a fixed-size visual detail must stay sharp regardless of text scaling, px is the honest choice.
A practical mixing strategy
The pattern that holds up in real stylesheets:
:root {
/* No px here — let the browser default (and the user) win. */
}
body {
font-size: 1rem; /* rem: respects user setting */
line-height: 1.5;
}
h1 { font-size: 2.5rem; } /* rem: no compounding, scales with user */
h2 { font-size: 2rem; }
.card {
padding: 1.5rem; /* rem: layout spacing scales globally */
border: 1px solid #e2e8f0;/* px: hairline stays crisp */
border-radius: 8px; /* px: fixed detail */
}
.card__title {
font-size: 1.25rem;
margin-bottom: 0.5em; /* em: gap proportional to THIS title */
}
Read it as three intentions: rem for anything a user should be able to scale, em for anything that should track its local text, and px for fixed pixel-precise details. If you can articulate which of the three applies, you have picked the right unit.
TL;DR
px is absolute and ignores the user's font-size; em is relative to the element's own font-size and compounds when elements nest; rem is relative to the root and never compounds. Default to rem for font-size and layout spacing because it respects the browser font-size setting (the WCAG 1.4.4 accessibility win), reach for em only when you want a value to track its local text (button padding, space after a heading), and keep px for the things that must stay pixel-fixed (1px borders, fine shadows). The conversion is just rem = px ÷ 16 — do it instantly with the px to rem converter, the rem to px converter, or the all-purpose unit converter. Pick the unit by answering one question: relative to what?
Keep reading
Related posts
HEX, RGB, HSL, and OKLCH Explained: A Practical Color Format Guide
HEX and RGB are the same sRGB color in different notation, HSL is a human-friendly transform, and OKLCH is perceptually uniform. Here is when to use each.
Read postUnix Timestamps and Epoch Time: The Complete Developer Guide
A Unix timestamp counts seconds since 1970-01-01T00:00:00 UTC, ignoring leap seconds. Learn seconds vs milliseconds, the Year 2038 problem, and conversion.
Read postOpen Graph & Meta Tags: The Complete Guide to Social Link Previews
The SEO title and meta description shape your SERP result; Open Graph and Twitter Card tags control how your link looks on Facebook, LinkedIn, X, and Slack.
Read post