CSS July 2, 2024 Aditya Rawas

Understanding CSS Units: The Complete Guide (px, em, rem, vw, dvh)

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 caseRecommended unit
Borders, shadowspx
Font sizesrem
Component-local spacingem
Full-width/height sectionsvw / dvh
Readable text columnsch
Fluid typographyclamp()
Consistent spacing scalerem 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

UnitEquivalentWhen to use
pt1/72 of an inchPrint stylesheets
pc12ptPrint stylesheets
cm / mmMetricPrint stylesheets
in96pxPrint stylesheets
Q0.25mmPrint (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:

UnitDescription
dvhDynamic viewport height (updates with browser chrome)
dvwDynamic viewport width
svhSmall viewport height (address bar visible)
lvhLarge 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 dimension
  • vmax = 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

UnitRelative ToBest Used For
pxScreen pixelBorders, shadows, precise layouts
remRoot font-sizeFont sizes, spacing — the default choice
emCurrent element font-sizeComponent-local spacing, button padding
%Parent elementWidths, fluid containers
vwViewport widthFull-width sections, fluid values
vhViewport heightDesktop full-height sections
dvhDynamic viewport heightMobile full-height layouts
svh / lvhSmall/large viewportWhen you need specific chrome state
chZero-character widthReadable prose column widths
clamp()Min/preferred/maxFluid typography and spacing

Key Takeaways

  • Use rem for font sizes and spacing — it respects user browser preferences and creates a consistent scale.
  • Use px only for borders, shadows, and fine details that shouldn’t scale.
  • Avoid px for font sizes — it breaks accessibility for users with larger font settings.
  • Use dvh instead of vh for full-height mobile layouts to avoid the address-bar jump bug.
  • Use ch to set readable prose widths: max-width: 65ch is 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.
Aditya Rawas

Written by

Aditya Rawas

Full-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.