px is an absolute, fixed unit; em is relative to the current element’s font-size and compounds through nesting; and rem is relative only to the root font-size, so it scales predictably without compounding. All three resolve to a number of CSS pixels in the end, but the rule each uses to get there changes how your layout responds to nesting, to user font preferences, and to responsive tweaks. Picking the right one per property is the difference between a design that respects accessibility settings and one that quietly ignores them.
What exactly is a CSS pixel?
A CSS pixel is an abstract unit of length, not necessarily one physical device pixel. On a standard-density display one CSS pixel maps to one hardware pixel, but on a high-density screen the browser may paint a single CSS pixel across four or more hardware pixels so that content stays a consistent physical size. That is why 16px text looks about the same size on a phone and a laptop despite wildly different pixel densities.
The important property of px for our purposes is that it is absolute within CSS: 20px is always 20 CSS pixels regardless of any font-size on the element or its ancestors. It does not scale with anything. That predictability is both its strength and its weakness — it never surprises you, but it also never adapts.
How does em resolve its value?
One em equals the computed font-size of the element the unit is used on — with a twist for the font-size property itself, where em refers to the inherited (parent) font-size. This makes em contextual. A padding of 1em on an element with a 20px font is 20px of padding; the same declaration on a 12px element is 12px of padding. Padding, margins, and gaps expressed in em therefore track the text size of their own element automatically, which is genuinely useful for building components that scale as a unit.
The catch is compounding on the font-size property. Consider nested elements that each set font-size: 1.25em:
<div class="a"> /* font-size: 1.25em -> 20px (from 16px root) */
<div class="a"> /* font-size: 1.25em -> 25px (1.25 x 20) */
<div class="a"> /* font-size: 1.25em -> 31.25px (1.25 x 25) */
</div>
</div>
</div>
Each level multiplies the last. Three levels of the same rule and the text is nearly double its starting size, even though every declaration looks identical. In a component library where you cannot predict nesting depth, this drift is a frequent source of “why is this text huge” bugs.
How is rem different from em?
rem stands for “root em” and is always relative to the font-size of the root element, the html tag — never to the current element or its parent. This single anchor point removes compounding entirely. 1.5rem means 1.5 times the root font-size everywhere in the document, no matter how deeply nested the element is. With the default 16px root, 1.5rem is 24px in the header, in a deeply nested list, and inside a modal alike.
That stability is why rem has become the default choice for font sizes and for many spacing values in modern stylesheets. You get relative scaling — change the root and everything moves together — without the fragility of em’s multiplication. The mental model is simple: define one base size at the root, express the rest of the type scale in rem, and the whole document breathes from a single control point.
| Unit | Relative to | Compounds? | Typical use |
|---|---|---|---|
| px | Nothing (absolute) | No | Hairline borders, fixed offsets |
| em | Current element font-size | Yes (on font-size) | Padding/margins that track local text |
| rem | Root (html) font-size | No | Font sizes, global spacing scale |
Why does the choice matter for accessibility?
Because users can change their browser’s default font-size, and only relative units respond to that change. A reader with low vision may set their browser default to 20px or 24px instead of 16px. When your type is sized in rem or em, that preference flows through: 1rem becomes 20px or 24px and your whole document scales up. When your type is hard-coded in px, it ignores the user entirely and stays at the size you picked.
This is the single strongest argument for reaching for rem on font sizes by default. It costs you nothing in visual design — you can still hit any pixel target you like at the default root size — but it preserves the user’s ability to make text larger through the mechanism browsers give them for exactly that purpose. A quick way to internalise the conversions is to run a few values through a client-side CSS units converter, which turns px into rem against a chosen root without sending anything to a server.
How do I convert between px and rem?
Divide the pixel value by the root font-size to get rem, and multiply rem by the root to get px. With the default 16px root the arithmetic is clean because 16 divides many common sizes evenly:
| Pixels (at 16px root) | rem |
|---|---|
| 12px | 0.75rem |
| 14px | 0.875rem |
| 16px | 1rem |
| 18px | 1.125rem |
| 24px | 1.5rem |
| 32px | 2rem |
Some teams historically set html { font-size: 62.5%; } to make the root 10px, so that 1.6rem equals 16px and the mental math becomes “move the decimal.” It works, but it also rescales any third-party CSS that assumes a 16px root, so weigh that trade-off before adopting it. Most modern codebases simply keep the 16px root and accept the slightly less round rem values.
When should I still use px or em?
Use px when you specifically want a value that does not scale, and use em when you want a value to track the font-size of its own element. A one-pixel border is the classic px case: you almost never want a hairline to grow to 1.5px when the user bumps their font size, and sub-pixel borders render inconsistently. Fixed small offsets — a 1px nudge to align an icon, a fixed focus-ring width — are reasonable in px too.
em shines for internal component padding. If you build a button whose font-size can vary, expressing its padding in em means a large button and a small button share the same proportions automatically: padding: 0.5em 1em gives half the text height above and below and a full text height on each side, at any size. Here compounding is a feature, because you want the padding to follow the local font. The trick is to keep em out of the font-size property in unpredictable nesting, where the multiplication bites.
A practical default that many teams settle on: rem for font sizes and global spacing, em for padding inside components that should scale with their own text, and px only for the handful of values you genuinely want frozen. Deciding per property rather than defaulting to one unit everywhere is what keeps a design both flexible and predictable — and it means the accessibility win from rem comes for free rather than being something you have to retrofit later.
It also helps to know which unit a given property expects to work well with. Line height is a special case: an unitless number like line-height: 1.5 is usually best, because it multiplies each element’s own font-size rather than inheriting a fixed computed length, so nested elements with different sizes all get proportional leading. Media query breakpoints are another place worth attention — expressing them in em means the breakpoints themselves respond to the user’s font-size, so someone with larger default text crosses to a roomier layout sooner. And for borders and shadows, where a crisp fixed appearance matters more than scaling, px remains the pragmatic default. None of these are hard rules, but they show the same principle at work: choose the unit whose scaling behaviour matches what that specific property should do when the context changes.