Why Use rem Instead of px? (The Accessibility Case)
Quick answer:remscales with the user's browser font size preference.pxignores it. When someone sets their browser to "Large" text (20px default instead of 16px),1rembecomes20pxwhile16pxstays at16px. For the 3-5% of users who change their default font size --- many of whom have low vision --- this is the difference between reading your content and not reading it.
Three years ago I audited a client's e-commerce site for accessibility. The entire CSS was in px. Every font size, every margin, every padding --- hardcoded pixels. The site looked sharp on my 27-inch monitor. On a browser set to 150% default font size (common for users over 60), the text was still the same hardcoded size. Navigation links at 14px stayed at 14px. Product descriptions at 15px stayed at 15px. The only way to make text larger was browser zoom, which blew up the entire layout and made the checkout form overflow its container.
The fix took two days. Replace px with rem on font sizes and spacing. The site now responds to user preferences without breaking. Here's why this matters and exactly how it works.
What Actually Happens When You Use px
A pixel is absolute. font-size: 16px renders at 16 CSS pixels regardless of what the user configured in their browser settings.
Browsers have a font size preference (Chrome: Settings > Appearance > Font size). The options are typically Very Small (12px), Small (14px), Medium (16px, the default), Large (20px), and Very Large (24px). This setting exists specifically for users who need larger text --- people with low vision, older users, or anyone using a high-DPI screen from a distance.
When you write font-size: 16px, you're telling the browser: "I know better than the user. Display this at 16px no matter what they've configured." The browser obeys. The user's preference is overridden.
This isn't a theoretical problem. The UK Government Digital Service tested their GOV.UK platform and found that approximately 4% of visitors had changed their default browser font size. WebAIM's 2024 screen reader user survey found that 27% of respondents used browser zoom or font size adjustments as an accommodation. These are real people on real websites.
What Actually Happens When You Use rem
rem is relative to the root element's (<html>) font size. The root font size defaults to whatever the user's browser preference is --- typically 16px, but potentially 12px, 20px, 24px, or any custom value.
/* If user's browser default is 16px (standard) */
h1 { font-size: 2rem; } /* 2 x 16 = 32px */
p { font-size: 1rem; } /* 1 x 16 = 16px */
/* If user's browser default is 20px (set by user with low vision) */
h1 { font-size: 2rem; } /* 2 x 20 = 40px */
p { font-size: 1rem; } /* 1 x 20 = 20px */
The proportions are identical. The heading is always 2x the body text. But the absolute size scales to match what the user asked for. The design stays coherent while respecting the user's needs.
This is the entire argument for rem. One unit. One behavior change. It turns your CSS from "ignore user preferences" to "respect user preferences." The CSS unit converter handles the px-to-rem math if you're converting an existing codebase.
Zoom Behavior: px vs rem Side by Side
Here's where it gets concrete. This table shows how text renders across different browser font size settings and zoom levels:
| Browser Font Setting | Zoom Level | 16px Renders As | 1rem Renders As | Difference |
|---|---|---|---|---|
| Medium (16px) | 100% | 16px | 16px | None |
| Medium (16px) | 125% | 20px | 20px | None |
| Medium (16px) | 150% | 24px | 24px | None |
| Large (20px) | 100% | 16px | 20px | rem is 25% larger |
| Large (20px) | 125% | 20px | 25px | rem is 25% larger |
| Large (20px) | 150% | 24px | 30px | rem is 25% larger |
| Very Large (24px) | 100% | 16px | 24px | rem is 50% larger |
| Very Large (24px) | 125% | 20px | 30px | rem is 50% larger |
| Very Large (24px) | 150% | 24px | 36px | rem is 50% larger |
rem faithfully delivers 25% larger text. px ignores the setting entirely and only grows with zoom.
A user who set their browser to "Very Large" (24px) is telling every website: "I need text at 150% of default." With px, they get nothing. With rem, they get exactly what they asked for. The layout scales proportionally because spacing (also in rem) grows along with the text.
The WCAG Connection
WCAG 2.2 Success Criterion 1.4.4 (Resize Text) requires that text can be resized up to 200% without assistive technology and without loss of content or functionality. The conformance level is AA --- the standard most organizations target for legal compliance (ADA, Section 508, EAA in Europe).
Using px for font sizes doesn't automatically violate SC 1.4.4, because browser zoom (Ctrl/Cmd + Plus) still works regardless of the unit. But it does violate the spirit of SC 1.4.4 and creates a worse experience for users who prefer the browser font setting over full-page zoom.
The practical difference between zoom and font size adjustment:
| Behavior | Browser Zoom (Ctrl+) | Browser Font Size Setting |
|---|---|---|
| What scales | Everything: text, images, layout, whitespace | Only text and rem-based values |
| Layout impact | Entire viewport shrinks (less content visible) | Layout stays the same width, text gets larger |
| User control | Coarse (125%, 150%, 200% steps) | Fine (any px value, typically 12-24px) |
| Persistence | Per-tab, per-site, sometimes lost on restart | Global, permanent across all sites |
| Mobile | Pinch-to-zoom (often disabled by sites with user-scalable=no) | Respected if rem is used |
Many accessibility consultants, including those at Deque Systems (makers of axe DevTools), explicitly recommend rem for font sizes as a best practice. It's not a WCAG requirement --- it's a UX best practice that makes WCAG compliance easier.
The Responsive Design Argument
Beyond accessibility, rem makes responsive design more predictable. Consider media queries:
/* px-based media query */
@media (min-width: 768px) {
.sidebar { display: block; }
}
/* rem-based media query (recommended) */
@media (min-width: 48rem) {
.sidebar { display: block; }
}
At default browser settings (16px root), 48rem equals 768px. Identical behavior. But if a user has set their browser font to 20px, 48rem becomes 960px. The sidebar only appears when there's genuinely enough space for both the content and the sidebar text at the user's preferred size. The px version shows the sidebar at 768px regardless, potentially cramming oversized text into a too-narrow container.
This is the subtle power of rem in media queries: your breakpoints adapt to the user's content size, not just the viewport size. The layout responds to actual content needs rather than arbitrary pixel thresholds.
Safari caveat: Safari handles rem in media queries inconsistently in some older versions. The widely accepted workaround is to use em for media queries specifically (not rem), since em in a media query context always references the browser default font size. @media (min-width: 48em) behaves identically to @media (min-width: 48rem) in all browsers except Safari, where em is more reliable.
What Should Stay in px
Not everything benefits from scaling. Some values should be absolute:
Borders. A 1px border should stay 1px. A 1rem border at 24px root font size becomes a 1.5px border --- the browser rounds to 2px, giving you a visibly thicker border than intended. Borders are visual accents, not content. They don't need to scale with font preferences.
.card {
border: 1px solid #e5e7eb; /* px: stays 1px always */
border-radius: 0.5rem; /* rem: scales proportionally */
padding: 1.5rem; /* rem: scales with font size */
box-shadow: 0 2px 4px rgba(0,0,0,0.1); /* px: shadow stays fixed */
}
Box shadows. Shadow offsets and blur radii are visual effects. A shadow that scales with font size looks wrong --- larger text doesn't need a proportionally larger shadow. Keep shadows in px.
Outlines and focus indicators. Accessibility focus indicators need to be consistently visible. A 2px outline is the WCAG-recommended minimum. Scaling it with rem could make it too thin at small root sizes or too thick at large ones.
Fine decorative details. Thin lines, dividers, underline offsets, small icon strokes --- anything where sub-pixel precision matters.
Here's a quick decision table:
| Property Type | Unit | Example |
|---|---|---|
| Font sizes | rem | font-size: 1.125rem |
| Spacing (padding, margin, gap) | rem | padding: 1.5rem |
| Max-width for text containers | ch or rem | max-width: 65ch |
| Border width | px | border: 1px solid |
| Border radius | rem or px | border-radius: 0.5rem |
| Box shadow | px | box-shadow: 0 2px 8px |
| Outline | px | outline: 2px solid |
| Media queries | em | @media (min-width: 48em) |
| Line height | Unitless | line-height: 1.5 |
| Letter spacing | em | letter-spacing: 0.02em |
rem and px, so you can pick whichever convention your project uses.
Migration Strategy: px to rem
If you have an existing codebase in px, here's the migration path I've used on three production sites:
Step 1: Don't touch the root font size. Leave html at the browser default (100% or 16px). Setting html { font-size: 62.5% } to make 1rem = 10px breaks third-party components that expect 1rem = 16px. It's a shortcut that creates more problems than it solves.
Step 2: Convert font sizes first. Font sizes have the biggest accessibility impact. Divide each px value by 16 to get rem. 14px = 0.875rem. 18px = 1.125rem. 24px = 1.5rem. Use the CSS unit converter for bulk conversion.
Step 3: Convert spacing (padding, margin, gap). Same formula: divide by 16. 8px = 0.5rem. 16px = 1rem. 24px = 1.5rem. 32px = 2rem.
Step 4: Leave borders, shadows, and outlines in px. These don't need to scale.
Step 5: Test at 150% and 200% browser font size. Set Chrome to "Very Large" (24px default) and check that nothing overflows, overlaps, or becomes unreadable. The most common breakage: fixed-width containers that can't accommodate larger text.
Common px-to-rem conversions (assuming 16px root):
| px | rem | Common Use |
|---|---|---|
| 10px | 0.625rem | Small labels, captions |
| 12px | 0.75rem | Secondary text, footnotes |
| 14px | 0.875rem | Body text (smaller), form labels |
| 16px | 1rem | Default body text |
| 18px | 1.125rem | Large body text, sub-headings |
| 20px | 1.25rem | H4, card titles |
| 24px | 1.5rem | H3, section headings |
| 30px | 1.875rem | H2, major headings |
| 36px | 2.25rem | H1, page titles |
| 48px | 3rem | Hero text, display headings |
FAQ
Do CSS frameworks like Tailwind already use rem?
Yes. Tailwind CSS uses rem for all spacing and font size utilities by default. text-base is 1rem (16px). p-4 is 1rem (16px) of padding. text-lg is 1.125rem (18px). If you're using Tailwind, you're already getting the accessibility benefits of rem without thinking about it. The only exception is border widths --- Tailwind's border is 1px, which is correct.
What if my designer gives me a Figma file with all px values?
Convert them. Figma works in pixels because it's a fixed-canvas design tool. The designer's intent is the visual proportion, not the literal pixel value. A heading at 32px in Figma becomes 2rem in CSS --- visually identical at default settings, but responsive to user preferences. Most design handoff tools (Figma's Dev Mode, Zeplin) can be configured to show rem values.
Does rem affect performance?
No. The browser resolves rem to a computed pixel value during the layout phase, the same phase where it resolves px. The resolved values are cached per element. There is zero runtime performance difference between 1rem and 16px --- they produce the same computed output. The unit choice is purely about maintainability and user experience.
Should I use rem for everything, including width and height?
For most widths, percentage (%) or viewport units (vw) are better than rem because widths typically relate to the viewport, not to font size. For max-width on text containers, ch (character width) or rem both work well. For height, avoid fixed values entirely when possible --- let content determine height. Use rem for min-height when you need a minimum vertical space.
Next Steps
- Convert your px values to rem with the CSS unit converter --- supports any base font size and handles bulk values.
- Read the full px vs rem vs em comparison for when to use
emoverrem. - Experiment with rem-based border radius values using the border radius generator --- live preview with copy-paste CSS output.