Somewhere in your audience is a reader whose browser default font size is 24px, set on purpose, and your site is ignoring them. Every line of body copy comes out at 16px anyway, because somebody typed it that way in a stylesheet two years ago.
The usual explanation for rem over px is that rem “scales” and px doesn’t. That explanation is wrong, and it sends people looking for the benefit in the wrong place. By the end of this post you’ll know what each unit is actually anchored to, which properties should listen to the reader, and which should keep their promise.
Zoom Scales Pixels Too
Press Ctrl and plus on any page built entirely in pixels. Everything grows: text, padding, borders, images, the whole layout. Modern browsers zoom by changing the ratio between a CSS pixel and a device pixel, so every length in your stylesheet moves together. The pixel you write is not a hardware pixel. It is a CSS pixel, a reference unit defined as 1/96 of an inch and mapped onto whatever the screen has.
The “px doesn’t zoom” advice dates from old browsers that resized text only. It stopped being true a long time ago, and it is still repeated in style guides as if it were current.
So if zoom isn’t the difference, what is? Browsers have a second control that most developers never touch: the default font size in settings. It ships at 16px, and people who need larger text change it once and expect every site to follow.
.card {
font-size: 16px;
padding: 24px;
width: 320px;
}
That card holds still. Zoom works on it, but the font-size setting does nothing to it, because a pixel value never consults the setting. The reader asked for bigger text in one place and got it everywhere except here.
What Rem Actually Multiplies
One rem is the computed font-size of the root element, the html tag. With nothing overridden, that is whatever the reader’s browser setting says. At the default it works out to 16px, so 1.5rem is 24px. If the reader raises the setting to 24px, the same 1.5rem becomes 36px.
Hold that sentence in your head and the whole unit makes sense. You are not writing a size. You are writing a ratio against a preference you don’t own. Here is the same card with its lengths converted, which only works because the root is still untouched.
.card {
font-size: 1rem;
padding: 1.5rem;
width: 20rem;
}
There is a sibling unit worth separating from it. em is relative to the element’s own font-size (or its parent’s, when you are setting font-size itself). That makes it the right unit for padding on a button, where you want the box to track the label inside it, and a trap for nested font sizes, where each level multiplies the last.
The number in rem is a ratio. The setting is the reader’s.
Unit_Doctrine // §1
Two Ways To Break It
The setting only reaches your layout if the root is left alone. There are two common ways to cut that wire, and both look harmless in review.
The first is the explicit pixel on the root. html { font-size: 16px } feels like a sensible reset, but it replaces the reader’s value with yours, and now every rem in the codebase is a disguised pixel.
html {
font-size: 16px; /* every rem below this line is now a pixel */
}
The second is the 62.5% trick. The idea is to make 1rem equal 10px, so 1.6rem reads as 16px and the arithmetic is easy. It does preserve the preference, because a percentage on the root multiplies the browser default instead of replacing it. What it breaks is everything you didn’t set. Any element that inherits the root size without an explicit rule, such as a form control or a third-party widget, renders at 10px.
html {
font-size: 62.5%; /* respects the setting, shrinks everything unstyled */
}
body {
font-size: 1.6rem; /* the line people forget */
}
My preference is to leave the root alone and do the division in your head, or in a token. A scale of 0.5rem, 1rem, 1.5rem, 2rem is easy enough once you’ve seen it twice. If you really want a clean base, font-size: 100% says the same thing the browser already assumes, and it makes the intent visible to the next person.
Breakpoints Count Too
Type isn’t the only thing that should hear the setting. Consider a reader at a 24px default on a 1024px laptop. Their text takes up half again as much room, so a sidebar-plus-article layout that was comfortable at 16px now squeezes the article into a column that holds far fewer characters per line.
A pixel breakpoint can’t see that. It asks about the viewport, and the viewport hasn’t changed. A breakpoint in rem or em can.
.layout {
display: grid;
gap: 1.5rem;
}
@media (min-width: 48rem) {
.layout {
grid-template-columns: 14rem minmax(0, 1fr);
}
}
At the default, 48rem is 768px. At a 24px default it is 1152px, so that reader gets the single-column layout on the same laptop, which is what you’d want for the text they chose.
One detail trips people here. Inside a media query, em and rem are both measured against the browser’s initial font-size, never against what your stylesheet sets on html. That is by design: a media query can’t depend on a declaration it is helping to select. So a root set to 62.5% does not change what 48rem means in a query, which is another reason the trick gets confusing.
Where Px Still Wins
None of this means every pixel in the file should be converted. Some measurements are about the screen, not the text, and they should hold still when the reader’s preference changes.
- Borders and hairlines. A 3px border should stay 3px. Scaled with the font, it turns into a 4.5px border and starts to feel like a different design.
- Offset shadows. The hard shadows in this site are 3, 5 and 8 pixels because they describe depth against the page, not against a letter.
- Fine alignment nudges. A 1px adjustment to line an icon up with a baseline is a pixel thing.
The rule underneath is a question you can ask of any length: does this measurement exist because of the text, or in spite of it? Padding around a paragraph exists because of the text, so it should follow it. A divider line exists in spite of it, so it shouldn’t.
| If the length is… | Reach for | Because |
|---|---|---|
| Type size, line spacing | rem | It should follow the reader’s setting |
| Space around text | rem | More text needs more room |
| Padding inside a control | em | The box tracks its own label |
| Line length | ch | Measured in characters, whatever the font |
| Borders, shadows, hairlines | px | They describe the screen, not the text |
| Breakpoints | rem or em | Layout should react when text gets bigger |
One thing to watch with fluid type. A size built purely from vw ignores the setting completely, because nothing in it refers to the root. Keep a rem term in the formula, as in clamp(1.5rem, 1rem + 2vw, 2.5rem), and the preference still counts for something.
The Preference Test
You can check all of this in under a minute. Open your browser settings, raise the default font size to the largest option, and reload your site. Then look at what didn’t move.
getComputedStyle(document.documentElement).font-size;
// "16px" at the default, "24px" after you raise it
If the console still says 16px after you changed the setting, something on the root is overriding it. If it says 24px and the body copy hasn’t grown, the text is in pixels somewhere below. Fix those two first, then walk the page for boxes that now overflow or collide.
Overflowing boxes are the useful finding. A card that breaks at 24px was relying on its text staying small, and that is a layout problem the reader would have hit eventually. The setting only made it visible sooner.
A pixel is a measurement of the screen, and a rem is a measurement of the person reading it. Most of your stylesheet should be the second kind.