Understanding CSS Units: The Complete Guide (px, em, rem, vw, dvh)
CSS units control the size, dimensions, and spacing of every element on your page. Choosing the wrong unit can make a layout brittle and hard to maintain; choosing the right one makes it flexible and responsive by default.
CSS units fall into two broad categories: absolute and relative. Let’s cover all of them with examples — and practical patterns for building real responsive layouts.
Quick Decision Guide
| Use case | Recommended unit |
|---|---|
| Borders, shadows | px |
| Font sizes | rem |
| Component-local spacing | em |
| Full-width/height sections | vw / dvh |
| Readable text columns | ch |
| Fluid typography | clamp() |
| Consistent spacing scale | rem with CSS custom properties |
Absolute Units
Absolute units are fixed — they don’t change based on other elements. Use them when you need precise, non-responsive dimensions.
Pixel (px)
The most common unit in web design. 1px = 1/96th of an inch at standard screen density.
.border {
border: 1px solid #ccc;
}
.shadow {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
Best for: Borders, box shadows, fine-grained layout control. Avoid using px for font sizes — it overrides user browser font preferences.
Other Absolute Units
| Unit | Equivalent | When to use |
|---|---|---|
pt | 1/72 of an inch | Print stylesheets |
pc | 12pt | Print stylesheets |
cm / mm | Metric | Print stylesheets |
in | 96px | Print stylesheets |
Q | 0.25mm | Print (rarely used) |
For screen-based design, px is the only absolute unit you’ll regularly use. The others are almost exclusively for print CSS.
Relative Units
Relative units scale based on other values. They’re the backbone of responsive design.
Root Em (rem)
Relative to the root element’s font-size (<html>). The most predictable relative unit.
html {
font-size: 16px; /* 1rem = 16px */
}
h1 { font-size: 3rem; } /* 48px */
p { font-size: 1rem; } /* 16px */
.card { padding: 1.5rem; } /* 24px */
Why rem beats px for font sizes: If a user sets their browser’s base font size to 20px (common for accessibility), rem values scale accordingly. px values ignore this setting.
Best for: Font sizes, spacing, padding, margin — anything that should scale proportionally with the user’s preferred font size.
Em (em)
Relative to the current element’s font-size. Unlike rem, it compounds when nested.
.parent {
font-size: 20px;
}
.child {
font-size: 1.5em; /* 30px (1.5 × 20px) */
padding: 1em; /* 30px (1 × child's font-size) */
}
.grandchild {
font-size: 1.5em; /* 45px (1.5 × 30px) — compounding! */
}
Best for: Component-local spacing that should scale with the component’s own font size — button padding, icon sizes relative to surrounding text.
Avoid for: Font sizes in deeply nested components, where compounding becomes hard to manage. Use rem instead.
Percent (%)
Relative to the parent element. Behaviour varies by property:
.container {
width: 80%; /* 80% of parent's width */
}
.text {
font-size: 120%; /* 120% of parent's font-size */
line-height: 150%; /* 150% of element's own font-size */
}
Best for: Widths of fluid containers, responsive images (width: 100%).
Viewport Units
vw and vh
1vw = 1% of the viewport width. 1vh = 1% of the viewport height.
.hero {
width: 100vw;
height: 100vh;
}
.sidebar {
width: 25vw;
}
The vh problem on mobile: On mobile browsers, 100vh includes the address bar. When the user scrolls and the address bar hides, 100vh suddenly changes, causing a jarring layout jump. This is the most common vh bug in mobile layouts.
dvh — Dynamic Viewport Height (The Modern Fix)
dvh updates dynamically as the browser chrome (address bar) shows or hides:
/* Old way — jumpy on mobile */
.hero {
height: 100vh;
}
/* Modern way — smooth on mobile */
.hero {
height: 100dvh;
}
The dynamic viewport units family:
| Unit | Description |
|---|---|
dvh | Dynamic viewport height (updates with browser chrome) |
dvw | Dynamic viewport width |
svh | Small viewport height (address bar visible) |
lvh | Large viewport height (address bar hidden) |
Browser support: dvh, svh, lvh are supported in all modern browsers (Chrome 108+, Safari 15.4+, Firefox 101+). Provide a vh fallback for older browsers:
.hero {
height: 100vh; /* fallback */
height: 100dvh; /* modern browsers */
}
vmin and vmax
vmin= 1% of the smaller viewport dimensionvmax= 1% of the larger viewport dimension
.icon {
font-size: 5vmin; /* scales with the smaller dimension */
}
Character Units
ch
Relative to the width of the 0 (zero) character in the current font. Excellent for controlling readable line lengths.
.prose {
max-width: 65ch; /* ~65 characters wide — optimal reading width */
}
Studies on typography suggest 45–75 characters per line is optimal for reading comfort. ch makes this trivially easy to implement without knowing the exact font size.
Fluid Typography with clamp()
clamp(min, preferred, max) returns the preferred value, clamped between min and max. Combined with viewport units, it creates typography that scales smoothly between breakpoints — no media queries needed.
h1 {
font-size: clamp(2rem, 5vw, 4rem);
/* min: 2rem (32px), preferred: 5% of viewport width, max: 4rem (64px) */
}
p {
font-size: clamp(1rem, 2.5vw, 1.25rem);
}
On a 320px viewport: 5vw = 16px, clamped to minimum 2rem (32px).
On a 1200px viewport: 5vw = 60px, clamped to maximum 4rem (64px).
In between: scales proportionally with the viewport.
Fluid Spacing with clamp
The same pattern works for spacing:
.section {
padding: clamp(2rem, 8vw, 6rem);
}
Building a rem-Based Spacing System
Rather than hard-coding rem values everywhere, define a spacing scale with CSS custom properties:
:root {
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
}
.card {
padding: var(--space-6);
margin-bottom: var(--space-8);
gap: var(--space-4);
}
This mirrors Tailwind’s spacing scale. Because everything derives from rem, a user who sets a larger base font size gets proportionally larger spacing throughout — fully accessible by default.
Full Reference Table
| Unit | Relative To | Best Used For |
|---|---|---|
px | Screen pixel | Borders, shadows, precise layouts |
rem | Root font-size | Font sizes, spacing — the default choice |
em | Current element font-size | Component-local spacing, button padding |
% | Parent element | Widths, fluid containers |
vw | Viewport width | Full-width sections, fluid values |
vh | Viewport height | Desktop full-height sections |
dvh | Dynamic viewport height | Mobile full-height layouts |
svh / lvh | Small/large viewport | When you need specific chrome state |
ch | Zero-character width | Readable prose column widths |
clamp() | Min/preferred/max | Fluid typography and spacing |
Key Takeaways
- Use
remfor font sizes and spacing — it respects user browser preferences and creates a consistent scale. - Use
pxonly for borders, shadows, and fine details that shouldn’t scale. - Avoid
pxfor font sizes — it breaks accessibility for users with larger font settings. - Use
dvhinstead ofvhfor full-height mobile layouts to avoid the address-bar jump bug. - Use
chto set readable prose widths:max-width: 65chis the sweet spot. - Use
clamp()for fluid typography that scales between a min and max without media queries. - Build a
rem-based spacing scale with CSS custom properties for consistency across your design system.
Written by
Aditya RawasFull-stack engineer writing deep-dives on JavaScript, TypeScript, React, AWS, Docker, and Kubernetes. Passionate about making complex engineering concepts accessible to developers at every level.