px, rem, em: What Each CSS Unit Actually Answers, and How to Convert Without Surprises
font-size: 16px and font-size: 1rem render identically for most users, which is how the "px and rem are interchangeable" myth survives. They answer different questions: px asks "how many pixels", rem asks "how many multiples of the root font size" — and the root follows the user's browser settings, while px ignores them. Once the boundary between the three units is clear, conversion is grade-school math; when it isn't, no formula will save you. For the arithmetic itself, the px ↔ rem converter handles both directions with a customizable root; pt/vw/em conversions live in the CSS unit converter.
The boundary line
| Unit | Relative to | Changes when | Use it for |
|---|---|---|---|
px | device pixels (CSS reference pixel) | nothing font-related | 1px dividers, box-shadow offsets, hairlines |
rem | the root element's font-size | user changes browser default size, or you change html{font-size} | typography at large: font sizes, spacing, radii, breakpoints |
em | the current element's font-size (inherited) | any ancestor's font changes | relative spacing within a component |
One-line memory hook: rem for global consistency, em for local proportion, px for physical fixation.
The conversion: formula and size table
rem = px / root-font-size (root defaults to 16px)
px = rem × root-font-size
Common values at root = 16 (this table ships inside the converter, annotated by role):
| px | rem | Role |
|---|---|---|
| 12 | 0.75 | fine print |
| 14 | 0.875 | compact body |
| 16 | 1 | root baseline |
| 18 | 1.125 | subheading |
| 24 | 1.5 | heading |
| 32 | 2 | large heading |
| 48 | 3 | display |
A precision detail: px→rem often yields infinite decimals (15/16 = 0.9375); 3–4 places are plenty, the browser parses no deeper. The reverse is always finite (rem × 16), so 2 places suffice.
Why typography bets on rem: the accessibility hard requirement
The browser's font-size setting (Chrome → Settings → Appearance) changes the default root font-size. A low-vision user raising it from 16 to 20 gets:
- an all-rem site: everything scales in proportion, layout intact;
- an all-px site: the setting does nothing;
- worst case, a rem/px mix: only the text grows while containers and spacing stay fixed — overflow, broken layout.
That's the test for "which properties deserve rem": anything that should track the user's reading size (font sizes, line heights, paddings, margins, text-container widths) goes rem; physical decoration (1px lines, shadow offsets) stays px. The browser minimum-font-size mechanism adds another reason — see the FAQ.
The em compounding trap
em resolves against the current element's font-size, which itself may inherit — nested lists are the classic incident:
li { font-size: 1.2em; } /* each level grows 20%: 1.2 × 1.2 × 1.2 … */
Three levels deep: 16 × 1.728 ≈ 27.6px, and the list visibly balloons. The same mechanism makes em-based paddings context-dependent — sometimes a feature (proportional rhythm), sometimes the bug that breaks alignment. Debugging technique: read the computed font-size in DevTools and walk the chain root-to-leaf until the multiplier stops matching your mental model.
The html { font-size: 62.5% } school: convenience and its price
Setting root to 10px makes conversion a decimal shift (2.4rem = 24px) with zero mental cost. The price: you've severed the browser default connection — users who deliberately set 20px/24px roots get slammed back to 10px, a straight accessibility regression; and some older engines clamp roots below 12px to their minimum, quietly distorting the 10px baseline.
The middle path: leave the root alone and outsource the arithmetic to tooling — editor extensions or a build-time PostCSS px2rem step. Mental math to the tools, accessibility dividend to the users.
The media-query rem trap
@media (min-width: 40rem) resolves rem against the root too — but Safari historically pins rem to 16px inside media queries, ignoring your overridden html. On a 62.5% site (root = 10px), the 40rem breakpoint is 400px in most browsers and 640px in Safari — a 240px divergence that forks your layout per engine. It's a second arrow in the 62.5% school's back, and the practical argument for "breakpoints in px or vw, content in rem".
Implementer's note
The site's converters are deliberately pure functions: pxToRem(px, root) keeps 4 decimals and trims trailing zeros (0.7500 → 0.75 — what you copy is a clean literal), remToPx keeps 2 (pixels don't need more). The root field is adjustable with a floor of 1 — 10 is a legal input (the 62.5% school is real), 0 or negative throws instead of dividing silently. The dual inputs stay linked but never locked: a hand-edited side isn't stomped by the next keystroke on the other — changed after annoying myself with it while writing docs. The role annotations in the size table (fine print / body / heading) follow Chinese web-typography conventions; the numeric values coincide with English type scales even where the naming doesn't.
FAQ
Is rem supported everywhere?
IE9+ shipped it; in 2026 support is a non-question. The only live edge is Safari's media-query rem behavior described above.
Does Chrome's minimum font size break rem layouts?
Chrome enforces a 6px floor and applies its font-size setting to some UI in special ways — under extreme settings, rem-sized text may render above its computed value. That's accessibility working as intended, not a bug. Layouts that survive such inflation are exactly what rem plus flexible containers buys you.
Line height: em or unitless?
Unitless (e.g. line-height: 1.5), always: it scales per-element and inherits as a ratio, while an em line-height bakes the computed value into inheritance — children whose font-size changes keep the stale height. A notorious inheritance trap worth memorizing on its own.
My design mockups are all px. How do I migrate to rem wholesale?
Divide by 16 (batch via the converter) or wire in a PostCSS px2rem step at build time. While you're at it, snap the spacing system to a 4/8 grid — otherwise the rem values will sprout ugly decimals forever.
These unit-boundary notes come from building the converters at ToolVault; the table and the link behavior are real implementation tradeoffs, not filler.
Field note
The dual-input sync was originally hard-wired (every edit stomped the other side) until writing this very documentation had me re-typing 1.1 → 1.125 three times through stomped intermediates — the most valuable QA engineer for a tool is always its first real user.
Related Tools
Related Articles
Online CSS Minifier: Reduce CSS File Size for Better Performance
Learn how to minify CSS safely: whitespace, comments, hex shortening, zero-unit elision, calc protection, and UTF-8 byte savings — with an online tool you can use locally.
Regex Not Matching? Troubleshoot These 6 Common Pitfalls
How to troubleshoot when a regex doesn't match or matching fails? This article walks through 6 common pitfalls — greedy quantifiers, missing flags, unescaped special chars, lookaround assertions, newline handling, and Unicode properties — with before/after examples to help you quickly locate regex debugging issues.
How to Fix 'Hydration failed / Text content does not match' in Next.js
The complete guide to Next.js/React SSR hydration errors: why server and client renders diverge, the three usual suspects (timestamps, random values, localStorage), correct suppressHydrationWarning usage, and a ClientOnly pattern for browser-only widgets.