Design files hand you pixels. Accessible CSS wants rem. The conversion itself is one division, but it’s easy to get wrong when the root font size isn’t 16px, when you’re converting a whole component at once, or when em sneaks in.
This guide covers the formula, a conversion table you can copy from, custom base sizes, and three ways to convert an entire stylesheet. If you’re still deciding whether to use rem, em, or px for a given property, start with PX vs REM vs EM: which units to use and when.
The px to rem formula
rem = px ÷ root font size
The root font size is the computed font-size of the <html> element. Browsers default it to 16px, so in most projects:
24px ÷ 16 = 1.5rem
14px ÷ 16 = 0.875rem
Going the other way, multiply: rem × root font size = px. So 2.25rem × 16 = 36px.
PX to REM conversion table (16px root)
| px | rem | px | rem |
|---|---|---|---|
| 1px | 0.0625rem | 24px | 1.5rem |
| 2px | 0.125rem | 28px | 1.75rem |
| 4px | 0.25rem | 32px | 2rem |
| 8px | 0.5rem | 36px | 2.25rem |
| 10px | 0.625rem | 40px | 2.5rem |
| 12px | 0.75rem | 48px | 3rem |
| 14px | 0.875rem | 56px | 3.5rem |
| 16px | 1rem | 64px | 4rem |
| 18px | 1.125rem | 72px | 4.5rem |
| 20px | 1.25rem | 96px | 6rem |
Every value on an 8px or 4px grid lands on a clean rem value, which is one reason those grids are popular.
When your root font size isn’t 16px
Some projects set a different root size. The formula doesn’t change; the divisor does.
18px root: 24px ÷ 18 = 1.333rem. Values stop being round, so round to three or four decimal places.
The 62.5% trick: setting html { font-size: 62.5%; } makes the root 10px (62.5% of the default 16px), so conversion becomes “move the decimal”: 24px = 2.4rem. Because it’s a percentage, it still respects users who change their browser’s default font size. The catch is that body text is now 10px by default, so you must set body { font-size: 1.6rem; } and remember that every third-party component using rem will render smaller than its authors intended.
Whatever you choose, never set the root in px (for example html { font-size: 10px; }). That overrides the user’s preferred font size, which defeats the accessibility reason for using rem in the first place.
Converting a whole stylesheet
Converting values one at a time gets tedious when you’re translating a full component from Figma. Three faster approaches:
1. Paste the block into a batch converter
The CSS Unit Converter has a Batch Conversion tab: paste the CSS, and each pixel value is shown with its rem and em equivalent as you type. Set the base size first if your root isn’t 16px.
/* Pasted */
font-size: 18px;
line-height: 28px;
padding: 12px 24px;
margin-bottom: 32px;
/* Converted at a 16px base */
font-size: 1.125rem;
line-height: 1.75rem;
padding: 0.75rem 1.5rem;
margin-bottom: 2rem;
2. Convert at build time with a Sass function
If you write Sass, keep pixel values in the source and let a function do the math:
@use "sass:math";
@function rem($px, $base: 16) {
@return math.div($px, $base) * 1rem;
}
.card {
padding: rem(24); // 1.5rem
border-radius: rem(12); // 0.75rem
}
3. Convert automatically with PostCSS
The postcss-pxtorem plugin rewrites pixel values to rem during the build, with options for which properties to convert and a minimum value to skip (useful for keeping 1px borders as pixels). It’s the least effort for an existing codebase, but check the output: converting everything blindly also converts values that should stay fixed.
Converting px to em
The em formula looks the same, but the divisor is the font size of the element’s parent, not the root:
em = px ÷ parent font size
Inside a button with font-size: 14px, padding of 7px is 0.5em. Move that button into a context where its font size is 18px, and the same 0.5em padding becomes 9px. That’s the point of em for component-internal spacing, and also why it compounds unpredictably when nested.
Details that trip people up
Keep hairlines in px. 0.0625rem is technically 1px, but at some zoom levels it can render blurry or disappear. Borders, outlines, and box-shadow offsets usually read better as pixels.
Use em in media queries. In media queries, both rem and em are relative to the browser’s initial font size rather than your html rule, and em has the more consistent history across browsers. @media (min-width: 48em) is 768px at the default size and scales if the user increases their font size.
Round sensibly. Three or four decimals is plenty. 1.3333rem and 1.33333333rem render the same.
Test at 200%. Change your browser’s default font size to 32px or zoom to 200%, and check that nothing overlaps or gets clipped. That’s the WCAG 1.4.4 resize-text requirement, and it’s what rem is for.
Quick reference
- Formula:
rem = px ÷ root font size(usually 16) - Reverse:
px = rem × root font size - em: divide by the parent’s font size instead
- Batch-convert a block: paste it into the CSS Unit Converter
- Deciding between units: read PX vs REM vs EM