Reference

Decision Tree

The right unit is the one that fails gracefully.

Forget the categories for a moment. In real work you don’t think “I need a viewport unit” — you think “I need this modal to fill the screen.” This page is organized by task, not by unit family. Find your scenario, get the answer and the reasoning.

The one question that solves most cases: relative to what should this size be? The element’s own text? Use em. The page baseline? rem. The window? vw/vh/dvh. The component’s box? cq*. The parent? %. Pin it exactly? px. Almost every decision below is a variation of that question.

Decision wizard · two clicks to the right unit

What are you sizing?

Prefer to browse? Every scenario the wizard covers is written out below, with the reasoning and the caveats.


Typography

Body text and headingsrem

body { font-size: 1rem; line-height: 1.5; }
h1   { font-size: clamp(2rem, 3vw + 1rem, 3rem); }

rem respects the user’s default font-size setting and never compounds through nesting. Keep line-height unitless so it inherits as a factor (not a fixed length) and stays proportional to each element’s own size.

A component’s internal proportions (button padding, gap to its icon) → em

.button { font-size: 1rem; padding: 0.625em 1em; border-radius: 0.5em; }

em makes padding scale with the button’s own text. Resize the button via font-size and every em-based dimension follows (a px border won’t).

Headline that scales with its container, not the windowcqi

.card { container-type: inline-size; }
.card__title { font-size: clamp(1rem, 5cqi, 2rem); }

Reading column widthch

.article { max-inline-size: 65ch; }

Aim for roughly 45–75 characters per line. ch is the advance of 0, so 65ch approximates that range rather than guaranteeing exactly 65 characters — proportional fonts vary (see the ch caveat).


Full-screen and viewport layouts

Mobile full-screen modaldvh

.modal { height: 100dvh; }

Tracks the currently visible viewport so bottom controls stay reachable as browser chrome moves. Use 100svh if you prefer stability over filling every pixel. Prefer dvh/svh over 100vh here: on current mobile browsers 100vh resolves like 100lvh, so it can hide content behind the chrome.

Landing hero with important content above the foldsvh

.hero { min-height: 100svh; }

The conservative viewport helps ensure the call-to-action is visible even when chrome is expanded (provided the content itself fits).

Immersive / full-bleed backgroundlvh

.cover { min-height: 100lvh; }

Cropping is acceptable here; you want maximum coverage.

App shell (chat, map, dashboard)dvh

.app { min-height: 100dvh; display: grid; grid-template-rows: auto 1fr auto; }

A square that always fits the screenvmin

.board { width: 90vmin; height: 90vmin; }

Layout and spacing

Page-level spacing scalerem

:root { --space-4: 1rem; --space-6: 1.5rem; }
.stack { display: grid; gap: var(--space-6); }

Stable everywhere, scales with the root, immune to nesting.

Proportional grid columnsfr

.layout { display: grid; grid-template-columns: 240px 1fr; }

Fixed sidebar, flexible content. Use minmax(0, 1fr) when a track holds long content that might overflow.

Fluid width relative to the parent%

.col { width: 50%; }

Aspect-ratio box (modern)aspect-ratio; legacy% padding

.ratio { aspect-ratio: 16 / 9; }            /* modern */
.ratio-legacy { padding-top: 56.25%; }       /* % of width, not height */

Borders, icons, and fine details

Hairline borders and crisp detailspx

.card { border: 1px solid; }

A clear case for px — values that should stay constant regardless of font or viewport (borders, shadows, hairline offsets, fixed breakpoints).

Icon the size of the text1em Icon the height of capital letters1cap Icon that fills one line1lh

.icon-text { width: 1em;  height: 1em;  }   /* matches type size */
.icon-cap  { width: 1cap; height: 1cap; }   /* matches capitals */
.icon-line { width: 1lh;  height: 1lh;  }   /* matches the line box */

Motion, rotation, and media

Transition / animation timings or ms

.fade { transition: opacity 200ms ease; }

1s = 1000ms. Remember: a bare number is invalid.

Rotation and gradientsdeg (or turn)

.spin { transform: rotate(45deg); }
.pie  { background: conic-gradient(red 0turn, blue 1turn); }

Serve sharper images to HiDPI screensdppx / x

@media (min-resolution: 2dppx) { /* … */ }

The quick table

You want… Use Why
Body text / headings rem Respects user prefs, no compounding
Component-internal proportions em Scales with the component’s text
Container-aware sizing cqi Responds to the box, not the window
Reading column width ch Tracks the font’s character width
Mobile full-screen modal dvh Fits the currently visible viewport
Hero with content above fold svh Conservative height — fits with chrome expanded
Immersive background lvh Maximum coverage, cropping ok
Square that fits the screen vmin Follows the limiting axis
Full-width section % Avoids 100vw scrollbar overflow
Grid columns fr Shares leftover space
Borders, hairlines px Must stay constant
Icon = text size 1em Matches the type
Icon = one line 1lh Matches the line height
Animation timing s / ms The only time units
Rotation / gradient angle deg / turn The angle units
HiDPI image switch dppx / x Density media queries

When you’ve matched a scenario, jump to the full unit article for the caveats. Or skim the Cheatsheet for every unit at a glance.


Sources