CSS Units Explained: px vs em vs rem vs % vs vw

- The Short Answer: Which CSS Unit to Use
- Absolute vs Relative Units
- px: The Fixed Unit
- em: Relative to the Element
- rem: Relative to the Root
- Percentages: Relative to the Parent
- vw and vh: Relative to the Viewport
- em vs rem: The Question Everyone Asks
- px vs rem: Which for Font Size?
- A Quick Decision Cheat-Sheet
- The Bottom Line
The Short Answer: Which CSS Unit to Use
CSS units fall into two camps: absolute units like px that never change, and relative units like em, rem, %, vw, and vh that scale against something else. The right choice depends on what you want to happen when a user zooms, changes their default font size, or resizes the window.
Here is the rule most professionals actually follow:
remfor font sizes, spacing, and most layout values — it respects the user's browser settings and stays predictable.pxfor things that should never scale:1pxborders, hairline dividers, box shadows.%for widths inside a flexible container.emfor spacing that should track the element's own font size (padding on a button, for example).vw/vhfor things sized against the viewport: full-height hero sections, fluid typography.
Everything below explains why each unit behaves the way it does, so you can reach for the right one without guessing.
Absolute vs Relative Units
An absolute unit is fixed. A px is a px no matter where you put it or what the user does. Absolute units include px, cm, mm, in, pt, and pc, but in practice px is the only one you'll use on screen — the physical units (cm, in, pt) are meant for print stylesheets.
A relative unit measures against a reference point. Change the reference and the computed value changes with it. This is what makes relative units powerful for responsive and accessible design: one rule can adapt to many contexts.
| Unit | Type | Relative to | Best for |
|---|---|---|---|
px |
Absolute | Nothing (fixed) | Borders, shadows, fine details |
em |
Relative | The element's own font-size |
Padding/margins that track text |
rem |
Relative | The root (<html>) font-size |
Font sizes, spacing, layout |
% |
Relative | The parent's corresponding value | Fluid widths |
vw |
Relative | 1% of viewport width | Fluid type, full-width elements |
vh |
Relative | 1% of viewport height | Full-height sections |
px: The Fixed Unit
px (pixel) is the CSS reference pixel. On modern high-density screens it isn't literally one hardware dot, but it stays visually consistent across devices.
.card {
border: 1px solid #ddd;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
The upside: total predictability. 16px is 16px everywhere.
The downside: it ignores user preferences. If someone bumps their browser's default font size up to 20px because they have trouble reading small text, a font-size: 16px rule stays stubbornly at 16. That's an accessibility problem. (Modern browsers still enlarge px on page zoom, but not when a user changes their default text size — the two are different settings.)
Use px for details that genuinely shouldn't scale with text, and reach for a relative unit for anything typographic.
em: Relative to the Element
1em equals the current element's font-size. If an element's font size is 20px, then 1em is 20px for that element — for padding, margins, widths, and even for a nested font size.
.button {
font-size: 18px;
padding: 0.75em 1.5em; /* 13.5px vertical, 27px horizontal */
}
Because the padding is in em, a bigger button (larger font size) automatically gets proportionally bigger padding. That's exactly what you want for a component that ships at several sizes.
The em compounding trap
em inherits, and that causes surprises when you nest. Font sizes multiply down the tree:
.menu { font-size: 1.2em; }
.menu li { font-size: 1.2em; } /* now 1.2 × 1.2 = 1.44em of the parent */
Three levels deep and your text is unexpectedly huge. This compounding is the single biggest reason developers moved font sizing to rem.
rem: Relative to the Root
rem means "root em." It's always relative to the font-size of the <html> element — no compounding, no matter how deep you nest. Since the browser default root size is 16px, 1rem is 16px unless you change it.
:root {
font-size: 100%; /* = 16px by default; respects user settings */
}
h1 { font-size: 2rem; } /* 32px */
p { font-size: 1rem; } /* 16px */
small{ font-size: 0.875rem; }/* 14px */
rem is the modern default for typography and spacing because it gives you two things at once: predictability (one reference point) and accessibility (it scales when a user changes their default font size). A handy trick is to build your spacing scale in rem so the whole layout breathes together when the root size changes.
If you're managing a design system, pair rem sizing with CSS custom properties so your scale lives in one place:
:root {
--space-1: 0.5rem;
--space-2: 1rem;
--space-3: 1.5rem;
}
Percentages: Relative to the Parent
% is relative to the parent's corresponding property — but which property matters, and it trips people up:
width: 50%→ 50% of the parent's width.height: 50%→ 50% of the parent's height (only works if the parent has an explicit height).padding: 10%andmargin: 10%→ always relative to the parent's width, even for top and bottom values.
That last rule is the classic gotcha. Vertical padding in percent is calculated from width, which is actually useful for maintaining aspect ratios but confusing the first time you meet it.
.column {
width: 33.333%; /* three across in a flexible row */
}
Percentages shine for fluid widths inside a container. For layout structure, though, Flexbox and Grid usually give you cleaner control — see how sizing interacts with spacing in the CSS box model.
vw and vh: Relative to the Viewport
Viewport units measure against the browser window itself:
1vw= 1% of the viewport width1vh= 1% of the viewport height1vmin= 1% of the smaller dimension1vmax= 1% of the larger dimension
.hero {
min-height: 100vh; /* fills the screen height */
}
Viewport units are unbeatable for full-bleed sections and fluid effects. But a few caveats:
100vwcan cause horizontal scroll because it includes the space a vertical scrollbar occupies. Prefer100%for full-width block elements.100vhon mobile historically overshot because of the dynamic address bar. Modern CSS addssvh,lvh, anddvh(small, large, and dynamic viewport height) to handle this —100dvhadapts as the mobile toolbar shows and hides. Browser support for these is solid across current Chrome, Safari, Firefox, and Edge, but check your minimum-support target before shipping.
Fluid typography with clamp()
Viewport units really pay off when combined with clamp() for text that scales smoothly between a floor and a ceiling:
h1 {
font-size: clamp(1.75rem, 1rem + 3vw, 3rem);
}
This reads as "never smaller than 1.75rem, never larger than 3rem, and in between grow with the viewport." No media query needed. This technique pairs naturally with a mobile-first CSS approach, where you design for the small screen first and let type scale up.
em vs rem: The Question Everyone Asks
They differ in exactly one way — their reference point.
em |
rem |
|
|---|---|---|
| Relative to | Element's own font-size | Root font-size |
| Compounds when nested | Yes | No |
| Best for | Component-local spacing | Global type & layout |
| Predictability | Lower (context-dependent) | Higher (single source) |
Practical guidance: use rem for font sizes across the site so nothing compounds unexpectedly. Use em inside a component when you want a value to track that component's text size — button padding, an icon sized to match the label, letter-spacing on a heading.
px vs rem: Which for Font Size?
Use rem. The reason is accessibility: a font size set in px ignores a user who has raised their browser's default text size, while rem honors it. Roughly 1 in 20 users runs a non-default font size, often because they need larger text — coding your type in px quietly locks them out.
Keep px for the things that should stay crisp regardless of text size: borders, outlines, and shadow offsets.
A Quick Decision Cheat-Sheet
| You're sizing… | Reach for |
|---|---|
| Body and heading font sizes | rem |
| Margins and padding (global spacing) | rem |
| Padding that should track a component's text | em |
A 1px border or hairline |
px |
| Column width in a flexible row | % |
| Full-height hero section | 100dvh (or 100vh fallback) |
| Type that scales with the screen | clamp() with a vw middle value |
The Bottom Line
You don't need every unit. A clean, accessible stylesheet mostly runs on rem for type and spacing, px for fine details, % for fluid widths, and vw/vh (or the newer dvh) for viewport-scaled effects. em earns its place for component-local sizing where you deliberately want values to follow the text.
Get those defaults into your muscle memory and the "which unit?" question mostly answers itself. When you're ready to see how units interact with sizing and spacing in practice, the CSS box model is the next piece of the puzzle.