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?
Relative to what should it scale?
What kind of surface?
What kind of measure?
What exactly?
Which dimension?
Body text and headings
rem
Respects the user’s default font-size and never compounds through nesting.
h1 { font-size: clamp(2rem, 3vw + 1rem, 3rem); }Read the full section →Component-internal proportions
em
Padding scales with the button’s own text — resize via font-size and everything follows.
.button { padding: 0.625em 1em; }Read the full section →Container-aware headline
cqi
Responds to the card’s box, not the window.
.card__title { font-size: clamp(1rem, 5cqi, 2rem); }Read the full section →Reading column width
ch
Tracks the font’s character width — aim for 45–75 characters per line.
.article { max-inline-size: 65ch; }Read the full section →Mobile modal / app shell
dvh
Fits the currently visible viewport, so bottom controls stay reachable as chrome moves.
.modal { height: 100dvh; }Read the full section →Hero with content above the fold
svh
The conservative viewport — your call-to-action stays visible even with chrome expanded.
.hero { min-height: 100svh; }Read the full section →Immersive background
lvh
Maximum coverage; cropping behind the chrome is acceptable here.
.cover { min-height: 100lvh; }Read the full section →A square that always fits
vmin
Follows the limiting axis of the viewport, portrait or landscape.
.board { width: 90vmin; height: 90vmin; }Read the full section →Full-width section
%
width: 100% avoids the 100vw scrollbar-gutter overflow; keep 100vw for deliberate bleed.
.section { width: 100%; }Read the full section →Page-level spacing scale
rem
Stable everywhere, scales with the root, immune to nesting.
:root { --space-4: 1rem; } .stack { gap: var(--space-4); }Read the full section →Grid columns sharing space
fr
A share of leftover space; use minmax(0, 1fr) when a track holds long content.
.layout { grid-template-columns: 240px 1fr; }Read the full section →Width relative to the parent
%
The classic fluid measure — resolved against the containing block.
.col { width: 50%; }Read the full section →Fixed proportion box
aspect-ratio
The modern property; the legacy trick is padding-top: 56.25% — a % of the width.
.ratio { aspect-ratio: 16 / 9; }Read the full section →Borders and hairlines
px
Values that must stay constant regardless of font size or viewport.
.card { border: 1px solid; }Read the full section →Icon matching the text
1em
The icon tracks the type size wherever the component lands.
.icon { width: 1em; height: 1em; }Read the full section →Icon matching the capitals
1cap
Capitals are what the eye aligns icons against — cap is that exact height.
.icon { width: 1cap; height: 1cap; }Read the full section →Icon filling one line
1lh
Matches the line box, so the icon slots into the text rhythm.
.icon { width: 1lh; height: 1lh; }Read the full section →Animation timing
s / ms
The only time units — and a bare number is invalid.
.fade { transition: opacity 200ms ease; }Read the full section →Rotation and gradients
deg / turn
deg for everyday angles; turn reads best for clean fractions of a circle.
.spin { transform: rotate(45deg); }Read the full section →HiDPI image switching
dppx / x
Density media queries and image-set() speak in dots per CSS pixel.
@media (min-resolution: 2dppx) { … }Read the full section →Prefer to browse? Every scenario the wizard covers is written out below, with the reasoning and the caveats.
Typography
Body text and headings → rem
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 window → cqi
.card { container-type: inline-size; }
.card__title { font-size: clamp(1rem, 5cqi, 2rem); }
Reading column width → ch
.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 modal → dvh
.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 fold → svh
.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 background → lvh
.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 screen → vmin
.board { width: 90vmin; height: 90vmin; }
Layout and spacing
Page-level spacing scale → rem
: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 columns → fr
.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 details → px
.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 text → 1em
Icon the height of capital letters → 1cap
Icon that fills one line → 1lh
.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 timing → s or ms
.fade { transition: opacity 200ms ease; }
1s = 1000ms. Remember: a bare number is invalid.
Rotation and gradients → deg (or turn)
.spin { transform: rotate(45deg); }
.pie { background: conic-gradient(red 0turn, blue 1turn); }
Serve sharper images to HiDPI screens → dppx / 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.