CSS has dozens of units, but the meaningful choice is between five or six. The choice affects not code aesthetics but behavior: whether the interface scales for a user with a large font, whether a component survives a move to a different context, how many media queries you'll have to write. Let's go through the working set.
px, rem, em: three frames of reference
px — an absolute unit. Predictable, but deaf to user settings: if the browser is set to a larger default font, px sizes won't respond.
rem — a fraction of the root font size (16px by default, changeable via the browser setting). 1.5rem = 24px for most people, but it will honestly grow for whoever set it larger. The rule in modern projects: font sizes — only in rem; spacing and radii — rem or px per the team's taste (rem makes the whole grid scale along with the text).
em — a fraction of the font size of the current element. Treacherous through cascading multiplication (em inside em inside em), but irreplaceable locally: a button's padding in em grows with the button's font — the component scales with a single font-size edit. The practical duo: global — rem, inside components — em, precise graphics — px.
There are content-based units too: ch — the width of a digit character, ideal for limiting text line length (max-width: 65ch), and lh — the line height.
Percentages: what matters is the base
A percentage is always computed from something on the parent, and "from what" depends on the property:
width: 50%— from the parent's width;height: 100%— from the parent's height, but only if it's set explicitly (otherwise ignored — the famous "height: 100% doesn't work");paddingandmarginin percentages — from the parent's width, even the vertical ones (a historical fact);font-size: 120%— from the parent's font (a synonym for 1.2em);transform: translateX(50%)— from the size of the element itself (which is why centering an absolute element isleft: 50%+translateX(-50%): the first percentage is from the parent, the second from itself).
Half of all "broken" percentages are a wrong guess about the reference base.
Viewport units: vw, vh and their mobile heirs
1vw/1vh — a percent of the window's width/height. Full-screen sections (min-height: 100vh), elements sized "to the whole screen" regardless of parents. Plus the derivatives vmin/vmax.
The mobile pain: the browser bar hides and slides back — the "window height" changes, and 100vh either scrolls or jumps. The spec's answer is three families: svh (small viewport — bar visible), lvh (large — bar hidden), dvh (dynamic — tracks the actual state). For full-screen blocks today you write min-height: 100dvh with a 100vh fallback one line above.
clamp: a fluid value with limiters
The function that ate half of all media queries:
h1 { font-size: clamp(1.5rem, 1rem + 2.5vw, 2.5rem); }
clamp(min, preferred, max) — the preferred value depends on the window width (via vw) but is pinned by the bounds. The heading grows smoothly from phone to monitor without a single breakpoint. The same technique works for section spacing (padding-block: clamp(2rem, 5vw, 6rem)) and widths. The rem + vw mix in the middle part matters: pure vw doesn't scale with the user's font setting, and the rem addition restores that link.
Nearby — min() and max(): width: min(90%, 1200px) — "full width, but no wider than the container" in one line, without max-width.
Media queries: what's left for them
Grid auto layouts, flexbox wrapping, and clamp cover a mass of "responsiveness" on their own. Media queries remain for structural rearrangements: hiding a sidebar, turning a menu into a burger, rearranging grid areas.
.layout { display: grid; grid-template-columns: 1fr; }
@media (min-width: 768px) {
.layout { grid-template-columns: 240px 1fr; }
}
The working approach is mobile-first: base styles for the narrow screen, min-width queries add complexity. Breakpoints are tied not to "iPhones" but to the content: where the layout breaks — that's the threshold. Beyond width, useful queries are (hover: hover) — to tell apart devices with a pointer (hover effects only there), and prefers-reduced-motion / prefers-color-scheme — respect for the user's settings. And for components that must respond to the width of their container, not the window, container queries have arrived — @container — the next step of the same idea.
In short
- Fonts — in rem (respect for user settings), spacing inside components — em, precise graphics — px, line length — ch.
- Percentages are computed from different bases: width — from the parent's width, height — only from an explicit height, padding/margin — always from the width, translate — from the element itself.
- Full-screen height on mobile —
100dvh(with a 100vh fallback): plain vh jumps because of the browser bar. clamp(min, rem+vw, max)— fluid typography and spacing without breakpoints;min(90%, 1200px)— a container in one line.- Media queries — for structural rearrangements, mobile-first, thresholds from the content;
hover: hoverand prefers-* are about devices and people, not pixels.
What to read next
- Grid — auto-fit/minmax: responsiveness built into the layout.
- The box model — how units add up into a box's size.
- Styling: gradients, shadows, and transforms — the final topic of the series.