Linked in both directions
Type in either field and the other follows on the keystroke. Change the root and the answer recalculates without losing what you typed, and pasting 24px or 1.5rem with its unit lands in the right field.
Convert pixels to rem and rem to pixels at whatever root font size your project uses, work out em for nested elements, and convert every px value in a stylesheet in one paste, with borders, media queries, and comments left alone.
24px is 1.5rem at a 16px root, and 1.5em in a 16px parent.
Every conversion on the page divides or multiplies by this.
Pick 10 for the 62.5% trick, where 1rem = 10px.
html { font-size: 100%; }100% is the browser default, so a 16px root needs no rule at all.
Read from this page once it has loaded. The site leaves the html font size alone, so it shows your browser's setting.
Trailing zeros are dropped, so 1.5000 is written 1.5.
Type in either field and the other follows. rem is measured against the root font size.
Exact at 4 decimal places, with trailing zeros dropped.
24px ÷ 16px root = 1.5rem
Each em font-size multiplies the one above it. List the font sizes from the root down and see the size each level ends up at, next to the rem that would not compound.
16px × 1.25 × 0.875 = 17.5px. Written in rem that is 1.0938rem, which stays the same size if an ancestor's font size changes.
Select a row to copy its rem value. The Tailwind column is the spacing step that gives the same rem value.
Tailwind's spacing scale steps by 0.25rem, so step 4 (p-4, m-4, gap-4) is 1rem: 16px at the default root. Only whole and half steps are listed; anything finer needs an arbitrary value such as p-[0.9375rem]. Tailwind v3's default scale also skips some steps (there is no 13), so check yours. The 1px row shows px because Tailwind's *-px utilities are exactly 1px at any root.
Paste CSS and every px value becomes rem at the 16px root, rounded to 4 decimal places. Each change is highlighted and listed by line.
/* Card component, sized in px. Notes such as 24px stay put. */
:root {
--radius: 0.75rem;
--gutter: 1.25rem;
}
.card {
padding: 1.5rem 2rem;
margin: 0 auto -0.5rem;
border: 1px solid #e3e6ea;
border-radius: var(--radius);
font: 0.9375rem/1.6 system-ui, sans-serif;
box-shadow: 0 0.0313rem 0.125rem rgb(0 0 0 / 0.08);
background: #fff url("img/grain-64px.png");
}
.card__title {
font-size: 1.375rem;
letter-spacing: -0.0313rem;
margin-bottom: 0.75rem;
}
.spec-label::after {
content: "16px";
}
@media (min-width: 768px) {
.card {
padding: 2rem 3rem;
}
}
This is a careful text transform, not a full CSS parser, so review the output before you ship it. It cannot see where a value is used: a breakpoint stored in a variable or passed to a mixin is converted where it is written, and so is a pixel offset into a pixel-sized sprite. Values inside an HTML style attribute sit in quotes and are left alone.
Convert the color values in the same stylesheet between HEX, RGB, and HSL, and check text contrast while you are there.
Work out the width and height of an image or video box at a fixed ratio before you size it in rem.
For physical lengths rather than CSS ones: the millimetres and inches on a print or product spec.
A rem is one root font size: the font size of the html element, which is 16px in every major browser until a site or a visitor changes it. Converting is one division, px ÷ root = rem, and one multiplication back, rem × root = px. em works the same way against a different number: the parent's font size for font-size itself, and the element's own font size for everything else. The arithmetic is simple; what this page adds is keeping the root, the rounding, and the edge cases straight.
Leave it at 16px, the default in every major browser, or pick the root your project sets on html. Choose 10px for the 62.5% setup, where 1rem is 10px. The settings also show the percentage to write in CSS, so a visitor's own font-size setting still counts.
Type pixels and read rem, or type rem and read pixels; the two fields stay linked. Switch the second field to em and give a parent size to convert nested elements. Pick 2 to 6 decimal places, and trailing zeros are dropped.
Paste CSS into the batch converter and every px value becomes rem, or the reverse. 1px borders, media queries, comments, strings, and url() values are left alone, each change is listed by line, and the result copies or downloads as a .css file.
Type in either field and the other follows on the keystroke. Change the root and the answer recalculates without losing what you typed, and pasting 24px or 1.5rem with its unit lands in the right field.
Convert against any parent size, then build a chain of nested em font sizes and see 0.875em inside 1.25em resolve to an exact pixel size, next to the rem value that would not have compounded.
Common sizes from 1px to 96px at the current root, each with its rem value and the matching Tailwind spacing step. Select a row and its rem value is on your clipboard.
The batch converter skips comments, quoted strings, url() values, and escaped class names, handles -0.5px and .5px, and keeps 1px hairlines as pixels if you ask. Every change is highlighted in the output and listed by line.
rem in a media query is measured against the browser's initial font size, not your html root. Breakpoints are left alone by default, and when you do convert them they are divided by 16, whatever the root is set to.
When a value does not divide evenly, the page says it was rounded and shows it to ten decimal places beside it, calling that exact only when nothing is cut. Rows in the reference table that are rounded are marked, and the batch converter counts every rounded value.
Divide the pixel value by the root font size: rem = px ÷ root. With the browser default root of 16px, 24px ÷ 16 = 1.5rem, 14px ÷ 16 = 0.875rem, and 12px ÷ 16 = 0.75rem. If your stylesheet sets a different root, divide by that instead: at a 10px root, 24px is 2.4rem. The root is the font size of the html element, not of body or of the element you are styling.
1rem equals the root font size, which is 16px in every major browser out of the box. It is not a fixed number, though. If a stylesheet sets html { font-size: 62.5% }, 1rem becomes 10px, and if a visitor raises the default font size in their browser settings to 20px, 1rem becomes 20px for them on every site that leaves the root alone. That variability is the point of the unit.
Multiply by the root font size: px = rem × root. At the default 16px root, 1.5rem × 16 = 24px, 0.875rem × 16 = 14px, and 2.25rem × 16 = 36px. When you check a build against a design file, remember that the result is the size for a visitor on default settings; someone who has chosen a larger default sees every rem value scaled up in proportion.
rem is always measured against the root (html) font size, so 1.5rem means the same thing anywhere on the page. em is measured against a local font size: for the font-size property it is the parent's font size, and for padding, margin, width, and other lengths it is the element's own computed font size. That makes em compound when nested. A list at 0.875em inside another list at 0.875em is 0.875 × 0.875 = 0.766 of the outer size, 12.25px from a 16px start. rem never compounds, which is why it is the usual choice for font sizes, while em stays useful for spacing that should scale with the text it surrounds, such as the padding of a button.
Because rem respects the visitor's default font size. Most desktop browsers let people raise that default, and many people with low vision do, and the setting changes the root, so anything sized in rem or em grows with it. A font size written in px ignores the setting entirely. Page zoom enlarges px and rem alike, so zoom is not the difference; the font-size preference is, and it is the setting people choose when they want larger text without everything else growing too.
62.5% of the 16px default is 10px, so it makes 1rem equal 10px and turns the arithmetic into a decimal shift: 24px is 2.4rem and 14px is 1.4rem. Because it is a percentage, it still respects a visitor's larger default (a 20px default becomes a 12.5px root). The trade-offs: text falls to 10px wherever you forget to set a size, so you need body { font-size: 1.6rem }; third-party components that assume a 16px root render at 62.5% of their intended size; Tailwind's spacing scale shrinks with it, so p-4 becomes 10px; and media queries ignore it entirely. Never set the root in px instead (html { font-size: 10px }), because that overrides the visitor's setting.
Usually not. A 1px border is a hairline meant to stay one. As 0.0625rem it grows with the root to fractional widths, such as 1.25px at a 20px root, and browsers typically snap border widths to whole device pixels, so lines can come out uneven from one element to the next. Thick decorative borders and outlines that should scale with the text are fine in rem or em. That is why the batch converter can keep 1px values as pixels while converting everything else.
It works, but it does not mean what it means elsewhere. Inside a media query, rem and em are both measured against the browser's initial font size, which is the visitor's default (16px unless they changed it), and never against the font size you set on html. So with html { font-size: 62.5% }, @media (min-width: 48rem) still switches at 768px on default settings, not at 480px. Breakpoints written in rem or em move to wider viewports when a visitor raises their default font size, so large text gets the roomier narrow layout instead of being squeezed into columns. This page leaves media queries alone by default, and when you choose to convert them it divides by 16, whatever root you have set.
Because most pixel sizes are not tidy fractions of the root. 15px is 0.9375rem and 13px is 0.8125rem at a 16px root, both exact; 10px at an 18px root is 0.5555 recurring. Four decimal places keep any 16px-root value within 0.0008px of the original, far below anything a screen can show, while two decimal places can be off by up to 0.08px. Trailing zeros are trimmed, so 1.5000 is written 1.5.
Mostly, as long as you review the result. The batch converter is a careful text transform rather than a full CSS parser: it skips comments, quoted strings, url() values, escaped class names, and media query conditions, and converts every other px value it finds. It cannot see where a value is used, so a breakpoint stored in a Sass variable or passed to a mixin is converted where it is written, and so is a background-position offset into a pixel-sized sprite. The change list shows every edit by line, so check those cases before you ship.
Explore the growing collection for calculations, documents, writing, and everyday work.