</>
DevToolHub
Developer workspace with code editor open showing CSS and HTML

px vs rem vs em: Which CSS Unit to Use (Decision Guide)

·11 min read
Quick answer: Use rem for font sizes, spacing, and most layout values --- it scales with the user's browser settings and keeps your design accessible. Use em for component-internal spacing that should scale relative to the component's own font size (like padding inside a button). Use px only for things that should never scale: borders, box shadows, and fine visual details. In 2026, rem is 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:

Propertypxremem
Relative toNothing (absolute)Root element font size (:root / html)Parent element font size
Default base1px = 1/96th of an inch1rem = 16px (browser default)1em = parent's computed font size
Responds to browser font settings?NoYesYes
Responds to zoom?YesYesYes
Predictable?Very --- same value everywhereVery --- always relative to one root valueLess --- depends on parent context
Compounds (nesting)?NoNoYes --- this is the gotcha
Best forBorders, shadows, fine detailsFont sizes, spacing, layout, media queriesComponent-internal padding, icon sizing
AccessibilityPoor for font sizesExcellentGood (but compounding is risky)
The single most important row: "Responds to browser font settings." When a user sets their browser default font to 20px (common for users with low vision), 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 PropertyRecommended UnitWhy
font-sizeremScales with user preferences, no compounding
line-heightUnitless (e.g., 1.5)Unitless line-height multiplies by the element's font-size automatically
paddingrem or emrem for layout spacing; em for component-internal spacing
marginremConsistent spacing regardless of component font size
width / max-widthrem, %, or chrem for fixed containers; % for fluid; ch for text containers
gap (flexbox/grid)remConsistent grid gaps across components
border-widthpxA 1px border should stay 1px; scaling borders looks odd
border-radiusrem or pxrem if it should scale with text; px for fixed rounding
box-shadowpxShadow offsets/blur don't need to scale with font size
outlinepxFocus outlines should be consistently visible, not variable
media queriesrem or emem is the Safari-safe choice (Safari handles rem in queries inconsistently)
letter-spacingemShould be proportional to font size
text-shadowpxLike box-shadow, fixed values look more intentional
For text-shadow styling, see the CSS text shadow examples for practical code you can copy.

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 SettingRoot Size16px Renders As1rem Renders As
Very Small12px16px (unchanged)12px
Small14px16px (unchanged)14px
Medium (default)16px16px16px
Large20px16px (unchanged)20px
Very Large24px16px (unchanged)24px
The 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.