CSS fundamentals · 6 / 8
lesson 6

Units that don't lie

Each unit is measured against something — once you know what, picking the right one is mostly obvious.

~ 18 min read·lesson 6 of 8
0 / 8

width: 200px says "two hundred pixels wide." width: 200% says… two hundred percent of what? width: 200em says two hundred em — and you may not know what an em is. Each CSS unit is measured against a specific thing, and the only confusing part of unit choice is forgetting what each one is measured against. This lesson makes that explicit. Once you can answer "200 of what?" for every unit, picking the right one is almost mechanical.

Pixels and the absolute units

The simplest unit is the pixel (px). One pixel is one pixel — give or take a layer of browser zoom and device-pixel-ratio maths that we will not get into here. For practical purposes, width: 200px always means a 200-pixel-wide box, regardless of font size or container size or screen size.

Pixels are an absolute unit: their value does not depend on anything else on the page. CSS has a few other absolute units (pt, pc, mm, cm, in) but they are basically only useful for print stylesheets. For screen work, px is the only absolute unit that comes up.

Pixels are great for things that genuinely should not change with the user's settings: a 1px border, a 16px icon, a hairline divider. They are bad for text size, because if a user has set their browser to a larger default font (for vision reasons), pixel-sized text will not scale up with that preference. The user's accessibility setting is silently ignored.

ok-vs-not.css
/* Fine — borders should not scale with font size. */
.card { border: 1px solid #ccc; }

/* Risky — locks text size against the user's default. */
body { font-size: 14px; }

The first rule sets a 1-pixel border. That should never change with browser zoom or font preferences — it is a structural detail. The second rule pins the body font to 14px. A user who has bumped their default to 20px (the browser setting) gets ignored: they still see 14-pixel text. The fix is one of the relative units coming up next.

em and rem — the typographic units

The two units you reach for constantly for text and text-adjacent spacing are em and rem. Both are measured against a font size, but a different one each.

em is measured against the font size of the element itself. padding: 0.5em on an element with a 16px font size is 8 pixels of padding; the same rule on an element with a 24px font size is 12 pixels. This is exactly what you want for a button: bigger buttons should naturally have bigger padding, and writing the padding in em lets it scale with the text inside.

button.css
.btn {
padding: 0.5em 1em;
border-radius: 0.25em;
}

A normal .btn with default 16px text gets 8px vertical and 16px horizontal padding. Add .btn.large { font-size: 24px } and the same rule gives the larger button 12px and 24px padding automatically — no second padding rule, no fighting. The em scales with the element's own type size.

The catch with em is compounding. An em is the current element's font size, so nested elements with an em-sized font size multiply: 1.2em inside 1.2em inside 1.2em ends up at 1.728× the root size. Set font sizes in em on a list of nested items and the deeper ones balloon. This is the classic "why are my deepest list items huge?" puzzle.

rem stands for "root em" and is always measured against the root element's font size — that is, the <html> element. By default that is 16px (the user-agent default), and crucially, it is the size the user can change in their browser preferences. rem is not affected by what any other element on the way down sets its font size to, so it does not compound. If you want everything tied to one consistent base, rem is the unit.

typography.css
html { font-size: 16px; } /* the base */
h1 { font-size: 2rem; }    /* 32px */
h2 { font-size: 1.5rem; }  /* 24px */
body { line-height: 1.5rem; } /* 24px, regardless of nesting */

Every value in rem resolves against html's 16px. Change html { font-size: 18px } and every rem-based value scales with it — including text. Most modern stylesheets use rem for type sizes, so a user who increased their default font size sees the whole page scale up gracefully.

A short, useful rule: rem for type sizes and global spacing, em for spacing inside a component that should scale with the component's own text.

check your understanding
You set html { font-size: 16px } .nav { font-size: 1.25rem } .nav .item { padding: 0.5em }. What is the padding on .nav .item?

Percent — the parent-relative one

% is the unit that always asks "percent of what?" — and the answer is almost always the parent. width: 50% means half the parent's width. padding-top: 25% means a quarter of the parent's width (yes, padding-top in percent is measured against width — this is one of the few real surprises in CSS).

layout.css
.container { width: 800px; }
.col { width: 50%; }       /* 400px */
.box { padding-top: 25%; } /* 200px — measured against parent width */

The first column rule gives you 400 pixels (half of 800). The padding rule gives you 200 — quarter of the parent's width, even though the property is padding-top. The reason is historical (it is how the spec defined it), and the practical use is the "aspect-ratio trick" for keeping a box at a fixed ratio. With modern CSS you should use the aspect-ratio property instead, but you will still see this pattern in older code.

Percent is great for layouts that share horizontal space (width: 33.333% on three columns) and bad for typography (font sizes in percent are an em in disguise — they compound the same way). When you reach for percent, double-check what parent you actually mean: a deeply nested element's percent is against its closest ancestor with a defined size, not the page.

Viewport units

The viewport is the visible area of the browser window — the rectangle the user can actually see. Viewport units measure against this rectangle, not against any element.

  • 1vw — 1% of the viewport's width.
  • 1vh — 1% of the viewport's height.
  • 1vmin / 1vmax — 1% of the smaller / larger of the two dimensions.

So width: 100vw always equals the full visible width, no matter where the element sits in the page. height: 100vh was the classic recipe for "make this section take a full screen," and it works — until you open it on a mobile browser, where the address bar's appearing-and-disappearing behaviour makes the visual viewport change height while the page is open. A 100vh hero would briefly overflow when the address bar collapsed.

The fix is dvh (dynamic viewport height) and friends. dvh updates with the visual viewport — when the address bar slides away, 100dvh shrinks to match. There are also svh (small — assumes the bars are visible) and lvh (large — assumes they are hidden) for the rare cases you want to pin to one or the other.

hero.css
/* Use this on modern projects. */
.hero { min-height: 100dvh; }

A hero section sized to 100dvh covers the visible viewport on every device, expanding and shrinking as the browser chrome shows and hides. On older browsers without dvh, 100vh is the fallback — they will see the older overflow behaviour, but the page still works.

Tip

Treat dvh as the modern default for "full-screen" sections; the old vh only matters if you specifically want a stable height that ignores the address bar.

check your understanding
You write .hero { height: 100vh } on a mobile site. What is the most common visual problem?

ch and the content-relative units

A few smaller units exist for narrow but useful jobs. The one worth memorizing now is ch — one ch is roughly the width of the digit "0" in the current font. It is content-relative in the most literal way: a unit that scales with whatever font you are reading.

The killer use of ch is line length. Readability research consistently lands on roughly 60–80 characters per line as the sweet spot for body text. Set max-width: 65ch on a paragraph or article and you get exactly that, regardless of the font you choose.

article.css
article p {
max-width: 65ch;
margin-inline: auto;
}

Every paragraph in articles gets a maximum readable width that adapts if you swap fonts. Switch from Georgia to Helvetica and the line length stays at 65 characters — the box width changes, but what the reader actually feels stays the same. That is the whole point of ch over px for line length.

A useful mental table to take away:

  • Borders, hairline rulespx. Should not scale.
  • Type sizes, base spacingrem. Tied to the user's font preference.
  • Inside-component paddingem. Scales with the component's own font size.
  • Layout columns%. Sharing the parent's width.
  • Full-screen sectionsdvh (with vh fallback). Tied to the visible viewport.
  • Reading widthch. Tied to characters, not pixels.
check your understanding
You want a paragraph to stay readable — about 70 characters wide — even after a designer swaps the body font. Which unit fits best?
check your understanding
A user has set their browser default font size to 22px. Your stylesheet says body { font-size: 14px }. Whose preference wins?
← prevnext lesson →
KeepLearningcertificate
for completing
CSS fundamentals
0 of 8 read