px vs rem vs em: Which CSS Unit to Use (Decision Guide)
Quick answer: Useremfor font sizes, spacing, and most layout values --- it scales with the user's browser settings and keeps your design accessible. Useemfor component-internal spacing that should scale relative to the component's own font size (like padding inside a button). Usepxonly for things that should never scale: borders, box shadows, and fine visual details. In 2026,remis the default choice for 90% of CSS values.
I used px for everything for years. Font sizes, margins, padding, media queries --- all pixels. It worked fine on my screen. Then a user with low vision emailed me: they'd set their browser default font to 24px, and my site completely ignored it. Every font was locked at the pixel values I'd hardcoded. They couldn't read my content without zooming the entire page to 200%, which broke the layout.
That email changed how I write CSS. Here's what I learned about the three units, and a framework for picking the right one every time.
The Three Units Compared
Here's the core difference in one table:
| Property | px | rem | em |
|---|---|---|---|
| Relative to | Nothing (absolute) | Root element font size (:root / html) | Parent element font size |
| Default base | 1px = 1/96th of an inch | 1rem = 16px (browser default) | 1em = parent's computed font size |
| Responds to browser font settings? | No | Yes | Yes |
| Responds to zoom? | Yes | Yes | Yes |
| Predictable? | Very --- same value everywhere | Very --- always relative to one root value | Less --- depends on parent context |
| Compounds (nesting)? | No | No | Yes --- this is the gotcha |
| Best for | Borders, shadows, fine details | Font sizes, spacing, layout, media queries | Component-internal padding, icon sizing |
| Accessibility | Poor for font sizes | Excellent | Good (but compounding is risky) |
rem-based layouts scale up proportionally. px-based layouts ignore the setting entirely. This isn't an edge case --- roughly 3-5% of users change their default font size, according to accessibility research from the UK Government Digital Service (GDS).
How px Works
A pixel is an absolute unit. 16px is 16px regardless of the browser settings, the parent element's font size, or anything else. It's the simplest CSS unit and the most predictable.
.box {
font-size: 16px; /* Always 16px */
padding: 24px; /* Always 24px */
border: 1px solid; /* Always 1px */
margin-bottom: 32px; /* Always 32px */
}
Pixels do respond to browser zoom (Ctrl/Cmd + Plus). When a user zooms to 200%, 16px renders at 32 physical pixels. This is fine --- zoom affects the entire viewport uniformly.
The problem is browser font size settings, which are different from zoom. Users can set their default font size in browser preferences (Chrome: Settings > Appearance > Font Size). This setting is meant to scale text without zooming the entire page. rem and em respond to it. px doesn't. A user who sets their browser to "Large" (20px default) expects text to be bigger, but font-size: 16px ignores that preference completely.
This is why WCAG 2.2 Success Criterion 1.4.4 (Resize Text) effectively requires that text can be resized to 200% without assistive technology. Using px for font sizes makes this harder to achieve. The CSS unit converter can convert your existing px values to rem if you're migrating an older codebase.
How rem Works
rem stands for "root em." It's relative to the root element's font size --- the <html> element. In every browser, the default root font size is 16px unless the user changes it.
html {
font-size: 16px; /* The default --- you don't need to set this */
}
.heading {
font-size: 2rem; /* 2 x 16 = 32px */
margin-bottom: 1.5rem; /* 1.5 x 16 = 24px */
}
.body-text {
font-size: 1rem; /* 1 x 16 = 16px */
line-height: 1.5rem; /* 1.5 x 16 = 24px */
}
The critical advantage: if a user sets their browser font to 20px, every rem value scales proportionally. 2rem becomes 40px instead of 32px. 1rem becomes 20px instead of 16px. The proportions stay the same, but the entire design gets bigger. No layout breaks, no overlapping text, no hidden content.
The 62.5% trick is a pattern you'll see in older codebases:
html {
font-size: 62.5%; /* 62.5% of 16px = 10px */
}
.heading {
font-size: 3.2rem; /* 3.2 x 10 = 32px */
}
This makes the math easier --- 1rem = 10px, so converting from pixel values is trivial. I used this for years. The problem: it forces every third-party component and library to compensate for your non-standard base size. Bootstrap, Material UI, and most component libraries assume 1rem = 16px. Use the 62.5% trick and their sizing breaks. In 2026, I recommend keeping the root at 100% (16px) and using a calculator for the conversion. The CSS unit converter does px-to-rem conversion with any base.
How em Works
em is relative to the parent element's computed font size. Not the root --- the parent. This makes em contextual, which is both its power and its biggest footgun.
.parent {
font-size: 20px;
}
.child {
font-size: 1.5em; /* 1.5 x 20 = 30px */
padding: 1em; /* 1 x 30 = 30px (relative to THIS element's font size) */
}
Wait --- why is the padding 30px and not 20px? Because once the .child element establishes its own font size (30px from 1.5em), its em-based padding references that computed font size, not the parent's. This is the compounding trap.
The Compounding Problem
.level-1 { font-size: 1.2em; } /* 1.2 x 16 = 19.2px */
.level-2 { font-size: 1.2em; } /* 1.2 x 19.2 = 23.04px */
.level-3 { font-size: 1.2em; } /* 1.2 x 23.04 = 27.65px */
.level-4 { font-size: 1.2em; } /* 1.2 x 27.65 = 33.18px */
Nesting four levels deep, each at 1.2em, and the font size has more than doubled from the root. In a deeply nested component tree (common in React/Vue), this gets out of control fast. I've debugged UIs where a 1.2em font size inside a card inside a sidebar inside a layout ended up rendering at 40px because of four levels of em compounding.
rem never compounds because it always references the root. That's why rem replaced em as the default unit for most values.
When em Is Still Useful
em shines for component-internal spacing that should scale with the component's font size:
.button {
font-size: 1rem; /* Base size from root */
padding: 0.5em 1em; /* Scales with font size */
border-radius: 0.25em; /* Scales with font size */
}
.button--large {
font-size: 1.25rem; /* Increase font size, padding auto-scales */
}
The large button's padding automatically increases because em is relative to the element's font size. You don't need a separate padding override for each button size. Change the font size, and the padding proportionally follows. This is genuinely useful for scalable component design.
Decision Framework
Use this table to pick the right unit for each CSS property:
| CSS Property | Recommended Unit | Why |
|---|---|---|
font-size | rem | Scales with user preferences, no compounding |
line-height | Unitless (e.g., 1.5) | Unitless line-height multiplies by the element's font-size automatically |
padding | rem or em | rem for layout spacing; em for component-internal spacing |
margin | rem | Consistent spacing regardless of component font size |
width / max-width | rem, %, or ch | rem for fixed containers; % for fluid; ch for text containers |
gap (flexbox/grid) | rem | Consistent grid gaps across components |
border-width | px | A 1px border should stay 1px; scaling borders looks odd |
border-radius | rem or px | rem if it should scale with text; px for fixed rounding |
box-shadow | px | Shadow offsets/blur don't need to scale with font size |
outline | px | Focus outlines should be consistently visible, not variable |
media queries | rem or em | em is the Safari-safe choice (Safari handles rem in queries inconsistently) |
letter-spacing | em | Should be proportional to font size |
text-shadow | px | Like box-shadow, fixed values look more intentional |
Real-World Migration: px to rem
Here's how a typical component looks in px versus rem:
/* Before: all px */
.card {
font-size: 16px;
padding: 24px;
margin-bottom: 32px;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.card__title {
font-size: 20px;
margin-bottom: 8px;
}
.card__body {
font-size: 14px;
line-height: 22px;
}
/* After: rem where it matters, px where it doesn't */
.card {
font-size: 1rem; /* 16px */
padding: 1.5rem; /* 24px */
margin-bottom: 2rem; /* 32px */
border: 1px solid #e5e7eb; /* stays px */
border-radius: 0.5rem; /* 8px, scales proportionally */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* stays px */
}
.card__title {
font-size: 1.25rem; /* 20px */
margin-bottom: 0.5rem; /* 8px */
}
.card__body {
font-size: 0.875rem; /* 14px */
line-height: 1.5; /* Unitless: 1.5 x computed font size */
}
Notice: border, box-shadow, and outline stay in px. Everything that relates to text readability or spatial layout moves to rem. The line-height loses its unit entirely --- unitless 1.5 means "1.5 times the element's computed font size," which adapts perfectly regardless of what unit the font size uses.
The CSS unit converter converts entire value sets from px to rem with a configurable base size.
The Accessibility Argument
This isn't hypothetical. Here's what happens at different browser font size settings:
| Browser Setting | Root Size | 16px Renders As | 1rem Renders As |
|---|---|---|---|
| Very Small | 12px | 16px (unchanged) | 12px |
| Small | 14px | 16px (unchanged) | 14px |
| Medium (default) | 16px | 16px | 16px |
| Large | 20px | 16px (unchanged) | 20px |
| Very Large | 24px | 16px (unchanged) | 24px |
px column is stuck at 16px regardless of the user's preference. The rem column respects it. For a user with low vision who set their browser to "Very Large," px-based text is 33% smaller than they requested. They'll either struggle to read it or reach for browser zoom, which blows up the entire layout instead of just text.
WCAG 2.2 doesn't explicitly say "use rem." But it requires that text can be resized to 200% without loss of content or functionality (SC 1.4.4). Using rem makes this trivially achievable. Using px makes it a constant fight against the browser's scaling behavior.
Common Mistakes
Mixing em and rem randomly. Pick a convention. I use rem for everything except component-internal padding (where em makes buttons/inputs self-scaling). Inconsistency means developers guess which unit to use, and you end up with both in the same component for no reason.
Setting html { font-size: 10px } to make rem math easier. This overrides the user's font preference, defeating the entire purpose of rem. If a user set their browser to 20px, your html { font-size: 10px } forces it back to 10px. Use html { font-size: 100% } (or don't set it at all) and do the conversion math with a tool.
Using em for line-height. If the font size changes (via media query or parent context), em-based line-height can create cramped or overly spacious text. Unitless values (just 1.5, not 1.5em) avoid this because they're always relative to the current element's computed font size, adapting automatically.
Forgetting that em on non-font properties uses the element's font size, not the parent's. padding: 1em on an element with font-size: 2rem gives you 32px of padding, not 16px. This catches people off guard when they expect em padding to match the parent's font size.
FAQ
Should I use rem for media queries?
Yes, and specifically em if you need Safari compatibility. Safari has a longstanding behavior where rem in media queries sometimes references the browser's default font size instead of the root element's computed size. em in media queries reliably references the browser's default in all browsers. @media (min-width: 48em) equals 768px at the default 16px base. This is one of the few cases where em is strictly better than rem.
Is there a performance difference between px, rem, and em?
No measurable difference. The browser resolves all relative units to computed pixel values during the layout phase. Whether you write 1rem, 16px, or 1em, the browser ends up with the same computed value. The calculation overhead is negligible --- browsers compute millions of property values per frame. Pick units based on maintainability and accessibility, not performance.
What about the ch unit?
ch equals the width of the "0" character in the current font. It's useful for constraining text containers: max-width: 65ch gives you roughly 65 characters per line, which is the optimal line length for readability (research from the Baymard Institute puts the sweet spot at 50-75 characters). I use ch exclusively for max-width on text blocks and rem for everything else.
Can I use CSS custom properties with rem?
Yes, and you should. Define your spacing scale as custom properties: --space-sm: 0.5rem; --space-md: 1rem; --space-lg: 1.5rem;. This gives you the benefits of rem (scaling) plus design tokens (consistency). Changing the root font size automatically scales every custom property that uses rem. The glassmorphism generator outputs CSS that follows this pattern.
Next Steps
- Convert your existing px values to rem with the CSS unit converter --- supports bulk conversion with configurable base size.
- Read why use rem instead of px for the deeper accessibility argument with zoom behavior comparison.
- Explore practical CSS effects with the glassmorphism generator --- all output uses rem-based sizing by default.