PX vs REM vs EM in CSS
Use rem for font sizes and most spacing, em for things that should scale with the text right next to them, and px for hairline borders, shadows and other details that should stay fixed. The reason is user settings: a reader who raises the default font size in their browser gets bigger text on rem-based sites, while text set in px stays the same size. 1rem equals the root font size, which is 16px unless the site or the reader changes it.
What each unit means
| Unit | Relative to | 1 unit equals (default) | Follows the user's font-size setting? |
|---|---|---|---|
| px | Nothing (a CSS reference pixel) | 1px | No |
| rem | Root element (html) font size | 16px | Yes |
| em | The element's own font size (parent's, when setting font-size) | Depends on context | Yes |
| % | Parent value for that property | Depends on context | Yes, for font-size |
| vw / vh | 1% of viewport width / height | Depends on screen | No |
| ch | Width of the “0” character | About 0.5em in many fonts | Yes |
A CSS pixel is not a device pixel. On a phone with a 3× display, 1px of CSS is drawn with three physical pixels in each direction. So px is a fixed unit of layout, not a promise about hardware.
Why rem beats px for font sizes
Browsers give people two ways to enlarge text. Zoom scales everything, px included. The default font size setting, found under appearance or font settings, changes only what 1rem and 1em resolve to. People with low vision often prefer the second, because it enlarges text without forcing horizontal scrolling. A site with font sizes in px ignores that setting; a site in rem honors it. WCAG success criterion 1.4.4 asks that text can be resized to 200% without losing content, and rem-based type makes that much easier to meet.
Converting is simple division by the root size: 14px is 0.875rem, 18px is 1.125rem and 32px is 2rem at the default 16px. The PX to REM Converter does single values and will also convert every px value in a pasted stylesheet.
When em is the better choice
Because em follows the element's own font size, it keeps proportions inside a component. Give a button padding: 0.5em 1em and the small, medium and large variants only need different font sizes; the padding follows. Icons sized at 1em match the text beside them. Margins between paragraphs in em scale with the type they separate.
The trap is compounding. Setting font-size: 1.2em on nested lists makes each level 20% larger than its parent, so the third level is 1.728 times the base. For font sizes, rem avoids this entirely. Reserve em for spacing and sizing that should track local text.
Where px is still right
- Borders. A 1px border is meant to be a hairline. At unusual zoom levels, 0.0625rem can round to zero or to two device pixels.
- Box shadows and outlines. Small offsets and blurs rarely need to grow with text.
- Images with fixed intrinsic sizes, such as a 32px avatar in a dense list, where the layout depends on the exact size.
Units in media queries
Inside a media query, both rem and em are based on the browser's initial font size, not on any font size your stylesheet sets on html. Breakpoints written in em or rem therefore move when a reader increases their default text, which is often what you want: a reader with 32px default text gets the narrow, single-column layout sooner. Breakpoints in px ignore the setting. Either is valid; just be consistent.
Fluid type with clamp() and viewport units
Viewport units alone are a poor choice for text, because font-size: 4vw ignores the user's font settings and becomes tiny on phones and huge on wide monitors. Combining them with rem inside clamp() fixes both problems:
h1 {
/* never below 2rem, never above 3.5rem, fluid in between */
font-size: clamp(2rem, 1.25rem + 3vw, 3.5rem);
}The rem part keeps the size tied to user preferences while the vw part lets it grow with the screen. For full-height mobile sections, prefer the newer svh, lvh and dvh units over vh, which on mobile browsers does not account for the address bar sliding in and out.
A sensible default setup
html { font-size: 100%; } /* respect the user's default */
body { font-size: 1rem; line-height: 1.5; }
h2 { font-size: 1.5rem; margin-block: 2em 0.5em; }
.btn { font-size: 0.875rem; padding: 0.5em 1em; border: 1px solid; }
.prose { max-width: 65ch; } /* comfortable line length */Note the unitless line-height: a bare number multiplies each element's own font size, so it never inherits a stale pixel value. And 65ch caps line length at roughly 65 characters, a comfortable reading measure.
Sizing is only half of readable text; color is the other half. Check your text and background pairs with the Color Contrast Checker and read WCAG color contrast explained for the thresholds. When the stylesheet is done, the CSS Minifier strips it down for production.