CSS Units Explained — When to Use px, em, rem, and vh
Photo by Hal Gatewood on Unsplash
Table of Contents
I Used px for Everything and My Responsive Design Was a Disaster
Pixels are absolute. They don't care about the user's font size preference, their browser zoom level, or their screen density. And that was fine in the era of 1024×768 monitors with no mobile web. But in 2026, where your CSS needs to work on a 27-inch 5K display and a $100 Android phone held in portrait mode, absolute units alone won't cut it.
I've spent the last two years gradually replacing `px` with relative units in my projects, and it's made a noticeable difference in how my layouts handle different screens and user preferences. Here's what I've learned about each unit and — more importantly — when to actually reach for each one.
px — Not Dead, But Not the Default
Here's where pixels make sense: - **Borders**: `border: 1px solid #e5e7eb` — you almost always want a hairline border regardless of context - **Box shadows**: `box-shadow: 0 4px 6px rgba(0,0,0,0.1)` — shadow offsets are visual, not typographic - **Small decorative details**: icon stroke widths, divider thicknesses, outline offsets
And here's where they don't: - **Font sizes**: If you set `font-size: 16px`, a user who's increased their browser's default to 20px (a common accessibility setting) won't see any difference. Your 16px overrides their preference. That's not just annoying — it's an accessibility failure. - **Spacing in components**: If you set `padding: 24px` on a card with `font-size: 14px`, the spacing holds. But if someone views it at 150% zoom for readability, the text gets bigger while the padding stays at 24px, and the proportions look off. - **Layout widths**: Setting `max-width: 600px` on a container works fine on most screens but might be too wide on a small phone or too narrow on a large tablet.
One thing that confuses people: CSS pixels aren't physical pixels. On a 2× Retina display, `1px` in CSS actually renders as 2 physical pixels. The browser handles this mapping through the `devicePixelRatio`. So `px` is already somewhat abstracted from hardware — it's a "reference pixel" that approximates 1/96th of an inch at arm's length. But it's still absolute in the sense that it doesn't scale with user preferences.
rem — The Unit I Use for Almost Everything Now
Photo by Balazs Ketyi on Unsplash
The reason I've switched to `rem` for most things: it respects the user's font size setting. If someone has their browser configured to use 20px as the default (which many visually impaired users do), all your `rem` values scale proportionally. Padding, margins, font sizes, even media query breakpoints — everything stays in proportion.
```css /* Instead of this */ h1 { font-size: 32px; } p { font-size: 16px; margin-bottom: 16px; }
/* Use this */ h1 { font-size: 2rem; } p { font-size: 1rem; margin-bottom: 1rem; } ```
The practical difference: if a user has their default set to 20px, the first version gives them a 32px heading and 16px body text (ignoring their preference). The second gives them a 40px heading and 20px body text (respecting their preference while keeping the 2:1 ratio). Same proportions, better accessibility.
**The 62.5% trick.** You'll see this in older codebases: ```css html { font-size: 62.5%; } /* Makes 1rem = 10px */ ``` Makes the math easier (1.6rem = 16px, 2.4rem = 24px), but I don't recommend it anymore. It breaks third-party components that assume a 16px root, and you have to explicitly set font sizes on everything since the body text is now tiny by default. It was clever in 2018 but creates more problems than it solves now.
**My actual approach:** I keep `html` at the browser default (16px) and use rem for everything. The math isn't that hard once you internalize the common values: 0.25rem = 4px, 0.5rem = 8px, 0.75rem = 12px, 1rem = 16px, 1.5rem = 24px, 2rem = 32px. If you're using a CSS tool to generate visual properties, the pixel values still appear — you just convert them when writing the actual code.
Tailwind CSS popularized this approach with its spacing scale, and there's a reason it works so well. You think in a consistent system instead of picking arbitrary pixel values. Your 4px/8px/16px/24px/32px scale becomes 0.25rem/0.5rem/1rem/1.5rem/2rem — same proportions, better scalability.
em — Useful But Tricky
```css .parent { font-size: 20px; } .child { font-size: 0.8em; } /* 16px */ .grandchild { font-size: 0.8em; } /* 12.8px — not 16px! */ ```
Each nested `em` multiplies against its parent, not the root. If you nest three levels deep with `0.9em` at each level, the innermost element gets `0.9 × 0.9 × 0.9 = 0.729` times the original size. This compounding effect is why `rem` replaced `em` for most use cases — `rem` always references the root, so nesting doesn't cause unexpected shrinking.
But `em` still has legitimate uses:
**Padding that scales with text.** If you have a button where the padding should proportionally increase when the font size increases, `em` is perfect:
```css .button { font-size: 1rem; padding: 0.5em 1em; /* scales with the button's own font size */ } .button-large { font-size: 1.25rem; /* padding automatically becomes proportionally larger */ } ```
This way, `.button-large` doesn't need separate padding values — the `em` units scale with the element's own font size. That's elegant and it's the one case where I consistently reach for `em` over `rem`.
**Media queries.** Here's a thing most developers don't know: `em` in media queries always references the browser default (16px), regardless of what you set on `html`. Safari has had historical bugs with `rem` in media queries. So `@media (min-width: 48em)` (768px) is actually more reliable than `@media (min-width: 48rem)` across browsers. It's a subtle edge case, but I've seen it cause real layout issues in Safari on iOS.
Viewport Units — vh, vw, dvh, and the Mobile Address Bar Problem
They're great for: - Full-screen hero sections: `height: 100vh` (well... sort of) - Typography that scales with screen width: `font-size: clamp(1rem, 2.5vw, 3rem)` - Elements that need to be a specific fraction of the screen
But `100vh` on mobile has been a headache for years. When mobile browsers show the address bar, the viewport is shorter. When you scroll and the bar collapses, the viewport gets taller. `100vh` gives you the LARGER measurement, meaning your "full-screen" section is actually taller than the visible area when the address bar is showing. The bottom of your content gets cut off.
The fix arrived with dynamic viewport units: - `dvh` (dynamic viewport height) — changes as the address bar appears/disappears - `svh` (small viewport height) — always uses the shorter measurement (bar visible) - `lvh` (large viewport height) — always uses the taller measurement (bar collapsed)
```css /* Old way — broken on mobile */ .hero { height: 100vh; }
/* Modern way — accounts for mobile address bar */ .hero { height: 100dvh; }
/* Fallback for older browsers */ .hero { height: 100vh; height: 100dvh; } ```
As of early 2026, `dvh`/`svh`/`lvh` are supported in all modern browsers according to MDN's compatibility data. I've stopped using `100vh` entirely — `100dvh` just works better everywhere.
A quick note on `vw` and horizontal scrollbars: `100vw` includes the scrollbar width on Windows, which means `width: 100vw` actually causes a horizontal scrollbar. Ironic, right? The fix is usually to not use `100vw` for full-width elements — `width: 100%` on a block element already fills its parent. Save `vw` for calculated values like responsive typography.
Speaking of layout — if you're deciding between Flexbox and Grid for your responsive layouts, the choice of CSS units matters there too. Grid's `fr` unit is another relative unit worth knowing. `1fr` means "one fraction of the available space," and it's the best way to divide grid space proportionally without calculating percentages.
The Cheat Sheet I Keep in My Head
| Use case | Unit | Why | |----------|------|-----| | Font sizes | `rem` | Respects user preferences, no compounding | | Spacing (margins, padding) | `rem` | Scales consistently with root | | Component-internal padding | `em` | Scales with the component's own font size | | Borders and shadows | `px` | Visual details that shouldn't scale with text | | Full-screen sections | `dvh` | Accounts for mobile browser chrome | | Responsive typography | `clamp()` with `rem` + `vw` | Fluid scaling between min and max | | Grid tracks | `fr` | Distributes remaining space proportionally | | Border radius | `px` or `rem` | `px` for subtle rounding, `rem` for large containers |
The `clamp()` function deserves a mention because it's changed how I think about responsive design. Instead of writing media queries to adjust font sizes at different breakpoints:
```css /* Old way — multiple breakpoints */ h1 { font-size: 1.5rem; } @media (min-width: 768px) { h1 { font-size: 2rem; } } @media (min-width: 1024px) { h1 { font-size: 2.5rem; } }
/* Modern way — one line */ h1 { font-size: clamp(1.5rem, 1rem + 2vw, 2.5rem); } ```
The heading smoothly scales between 1.5rem and 2.5rem based on viewport width, with no breakpoints needed. I use this for pretty much every heading and a lot of body text now. It's one of those CSS features that makes you wonder how you ever lived without it.
Just make sure your minimum value is in `rem` so it still respects user font preferences — don't use `px` for the minimum in a `clamp()` that controls text size. That defeats the whole purpose of using relative units in the first place.
Frequently Asked Questions
What's the difference between em and rem in CSS?
rem is relative to the root element's font size (usually 16px), while em is relative to the parent element's font size. The key difference: rem values are consistent everywhere on the page, but em values compound when elements are nested. If a parent has font-size: 20px and a child uses 0.8em, that's 16px. But if a grandchild also uses 0.8em, it's 12.8px (0.8 of the parent's 16px), not 16px again. This compounding is why rem is generally preferred for consistent sizing.
Should I use px or rem for font sizes in CSS?
Use rem for font sizes in almost all cases. When you set font-size in pixels, you override the user's browser font size preference, which is an accessibility problem. Users who've increased their default font size (common for visually impaired users) won't see any change if your font sizes are in px. With rem, your text scales proportionally with their preference. The exception: you might use px for very specific UI elements where exact sizing is critical and scaling would break the layout.
Why doesn't 100vh work properly on mobile browsers?
Mobile browsers have a collapsible address bar that changes the viewport height. 100vh uses the largest possible viewport height (with the bar hidden), so when the bar is visible, your element is taller than the screen and content gets cut off at the bottom. The fix is to use 100dvh (dynamic viewport height), which adjusts as the bar appears and disappears. As of 2026, dvh is supported in all modern browsers.
What is the CSS clamp() function used for?
clamp() creates fluid responsive values without media queries. It takes three arguments: a minimum, a preferred value, and a maximum. For example, font-size: clamp(1rem, 2.5vw, 2rem) sets text that's at least 1rem, at most 2rem, and scales smoothly with viewport width in between. It's most commonly used for responsive typography and spacing, replacing multiple breakpoint-based media queries with a single declaration.
Which CSS unit is best for responsive web design?
There isn't one best unit — it depends on what you're sizing. Use rem for font sizes and spacing (respects user preferences), em for component-internal padding that should scale with the component's text, px for borders and shadows, dvh for full-screen sections on mobile, vw inside clamp() for fluid typography, and fr for CSS Grid tracks. The key principle: use relative units for anything that should adapt to context, and absolute units only for fixed visual details.
Try ToolsFuel
23+ free online tools for developers, designers, and everyone. No signup required.
Browse All Tools