Developer FIELD NOTES · 5 MIN READ
rem vs em vs px: CSS Units Explained
How px, em and rem really differ, the em compounding trap, the 62.5% root trick, and a decision rule for sizing text, spacing and borders.
Pick any CSS length property and you have a choice of units — px, em, rem, %, vw — and the choice quietly decides whether your page adapts to the user's font-size preference or ignores it. Three units do most of the work: px is fixed, em scales with the element's own text, rem scales with the page's root text. Here is exactly what each one measures.
px: fixed on purpose — and not really a pixel
The CSS pixel is not a hardware pixel. It is a reference pixel: 1px is meant to subtend the same angle of view on every device, so on a high-DPI phone one CSS px may span several physical pixels. In practice, think of px as a device-independent fixed unit. Two consequences: border: 1px gives a hairline you can rely on at any zoom level, and nothing in px responds when a user raises their browser's default font size. That second consequence is the accessibility catch — a page sized entirely in px renders identically for a user who prefers 24px body text, which usually means it renders wrong.
em: relative — to two different things
An em is a multiplier of a font size — but which font size depends on the property. For font-size itself, em refers to the parent's font size. For every other property — padding, margin, width, border-radius — em refers to the element's own font size. That split makes em powerful and treacherous in nesting:
body { font-size: 16px } /* 16px */
.widget { font-size: 1.2em } /* 1.2 × 16 = 19.2px */
.widget span { font-size: 1.2em } /* 1.2 × 19.2 = 23.04px */
Each nesting level multiplies again — 1.2em of 1.2em of 16px is 23.04px, not 19.2px. Deeply nested UIs drift upward like this, which is the classic em bug. The upside is the flip side: padding: 1em grows with the element's text, so a button's padding automatically breathes with its label. Spacing that should track text size is em's natural home.
rem: one ruler for the whole page
rem ("root em") is a multiplier of the root element's font size — the <html> element — no matter where it appears in the document. Browsers default the root to 16px, so the common conversions are fixed arithmetic:
0.75rem = 12px 1rem = 16px
1.25rem = 20px 1.5rem = 24px
2rem = 32px 3rem = 48px
Because every rem value traces back to one root, there is no compounding and no drift. The accessibility win is structural: if a user sets their browser default to 20px, the root becomes 20px and every rem-based size on the page scales up proportionally — the layout resizes around their preference without a media query in sight. A popular trick, html { font-size: 62.5% }, makes 1rem equal 10px so mental math is trivial (1.6rem = 16px); the caveat is that it hardcodes your own root value and partially overrides the user's preference, so some teams adopt it and some refuse it.
A decision rule
Font sizes: rem — they should follow the user's preference, and rem is immune to the compounding that plagues em. Spacing inside components (padding on a button or badge): em, so spacing scales with the component's own text; page-level spacing and layout gutters: rem. Hairlines, borders, and box-shadows: px — a 1px border should stay 1px. Media queries in modern browsers are effectively em-based regardless of what you write, so their breakpoints already follow the root font size. Percentages remain the tool for widths that fill a parent — width: 50% is relative to the parent's width, not any font size, which is a different axis entirely.
One card, three units
.card {
font-size: 1rem; /* 16px at the default root */
padding: 1em; /* 16px here — tracks this card's text */
border: 1px solid #ddd; /* hairline stays a hairline */
margin-bottom: 1.5rem; /* 24px — page-level rhythm */
border-radius: 0.5rem; /* 8px */
}
/* User sets browser default to 20px: font 20px, padding 20px,
margin 30px — all scaled. Border still 1px. */
Read the comment: the rem and em values scale together when the user's base size changes, while the border holds its hairline. That mixture — rem for the global scale, em for text-local spacing, px for physical detail — is the idiom most design systems converge on.
Units at the byte level
Minifiers squeeze units, not just whitespace. A zero length needs no unit (margin: 0 beats margin: 0px), leading zeros vanish (0.5rem → .5rem), and comments and semicolons before closing braces disappear. Multiplied across a stylesheet, that is real payload — and it is also why unit choice affects more than rendering: consistent rem values compress and deduplicate better than dozens of ad-hoc px values. One honest note about conversion math: an em's true size depends on the element it appears in, so any px↔em conversion is only as good as the font-size context you feed it — treat results as estimates for the context you specify.
Try it yourself
Convert values both ways with the CSS Unit Converter — px, rem, em, pt, vw, vh, and % — including adjusting the root font size to see how a user preference changes every answer. Feed a stylesheet to the CSS Minifier and watch the unit-level savings described above. And for one of the most fiddly unit tasks, the Border Radius Generator builds four-corner radius values in px, %, em, or rem with a live preview, so you can see how 50% turns a square into a circle.