---
title: 'CSS Units'
source: 'https://academia.sh/en/courses/css-fundamentals/css-units'
course: 'Visual Presentation with CSS'
language: en
updated: '2026-08-17T18:09:19+00:00'
license: 'CC BY-SA 4.0'
---

# CSS Units

Absolute, font-relative, and viewport-relative units; the em chain's calculation, rem's stability, and what a percentage resolves against.

Throughout this topic, lengths got written as pixels and `rem`, and
the `ch` unit got left undefined. A length can get written in several
separate forms, and the choice is not just a number format: some
units track user settings, some do not.

This lesson separates units into three families and shows the
conversions between them with calculation.

## Three Families

**Absolute units** — `px`, `cm`, `mm`, `in`, `pt`, `pc`, `q`. The
ratios between them are fixed, and `1in = 96px` gets taken by
definition. `px` does not correspond to a device pixel; it is a
reference unit and stays close to the same physical size regardless
of the device pixel ratio.

**Font-relative units** — `em`, `rem`, `ch`, `ex`, `cap`, `lh`, `rlh`.
Their value depends on the font metrics in the context they get
calculated in.

**Viewport-relative units** — `vw`, `vh`, `vmin`, `vmax`, and their
dynamic forms. They are defined as one percent of the viewport.

```js
// units.mjs — the em chain, rem stability, and percentage references
console.log("--- em chain: multiplied again at every generation ---");
const chain = [
  { name: "html",    fontSize: "16px" },
  { name: "body",    fontSize: "1em"  },
  { name: "section", fontSize: "1.25em" },
  { name: "div",     fontSize: "1.25em" },
  { name: "p",       fontSize: "1.25em" },
  { name: "span",    fontSize: "1.25em" },
];
let parentPx = 16;
for (const node of chain) {
  let px;
  if (node.fontSize.endsWith("px")) px = parseFloat(node.fontSize);
  else px = parentPx * parseFloat(node.fontSize);
  console.log(`${node.name.padEnd(8)} font-size: ${node.fontSize.padEnd(7)} -> ${px.toFixed(3)}px  (parent ${parentPx.toFixed(3)}px)`);
  parentPx = px;
}

console.log("\n--- the same chain with rem ---");
const ROOT = 16;
parentPx = 16;
for (const node of chain) {
  const px = node.fontSize.endsWith("px") ? parseFloat(node.fontSize) : ROOT * parseFloat(node.fontSize);
  console.log(`${node.name.padEnd(8)} font-size: ${node.fontSize.replace("em","rem").padEnd(8)} -> ${px.toFixed(3)}px`);
}

console.log("\n--- em on the same element: font-size uses the outer value, padding the inner ---");
const parentFont = 16, ownFont = 1.5 * parentFont;
console.log(`parent font-size = ${parentFont}px`);
console.log(`font-size: 1.5em -> ${ownFont}px   (via the parent's value)`);
console.log(`padding:   1.5em -> ${1.5 * ownFont}px   (via its own font-size)`);
console.log(`line-height: 1.5em -> ${1.5 * ownFont}px`);

console.log("\n--- what a percentage resolves against ---");
const CONTAINING = { width: 640, height: 400, fontSize: 16 };
const REFERENCE = {
  "width":       ["containing width", CONTAINING.width],
  "padding-top": ["containing WIDTH", CONTAINING.width],
  "margin-left": ["containing width", CONTAINING.width],
  "height":      ["containing height", CONTAINING.height],
  "font-size":   ["parent font-size", CONTAINING.fontSize],
  "line-height": ["own font-size", 16],
};
for (const [prop, [description, base]] of Object.entries(REFERENCE)) {
  console.log(`${prop.padEnd(12)} %50 -> ${(base * 0.5).toFixed(1)}px   (reference: ${description} = ${base})`);
}

console.log("\n--- absolute unit conversions (1in = 96px) ---");
const PX = { px: 1, in: 96, cm: 96/2.54, mm: 96/25.4, q: 96/101.6, pt: 96/72, pc: 16 };
for (const [unit, value] of Object.entries(PX)) console.log(`1${unit.padEnd(3)} = ${value.toFixed(4)}px`);

console.log("\n--- ch and ex (in an example font) ---");
const size = 16, chRatio = 0.5, exRatio = 0.52;
console.log(`font-size=${size}px, 1ch=${size*chRatio}px, 1ex=${(size*exRatio).toFixed(2)}px`);
console.log(`max-width: 70ch -> ${70 * size * chRatio}px`);
```

```
--- em chain: multiplied again at every generation ---
html     font-size: 16px    -> 16.000px  (parent 16.000px)
body     font-size: 1em     -> 16.000px  (parent 16.000px)
section  font-size: 1.25em  -> 20.000px  (parent 16.000px)
div      font-size: 1.25em  -> 25.000px  (parent 20.000px)
p        font-size: 1.25em  -> 31.250px  (parent 25.000px)
span     font-size: 1.25em  -> 39.063px  (parent 31.250px)

--- the same chain with rem ---
html     font-size: 16px     -> 16.000px
body     font-size: 1rem     -> 16.000px
section  font-size: 1.25rem  -> 20.000px
div      font-size: 1.25rem  -> 20.000px
p        font-size: 1.25rem  -> 20.000px
span     font-size: 1.25rem  -> 20.000px

--- em on the same element: font-size uses the outer value, padding the inner ---
parent font-size = 16px
font-size: 1.5em -> 24px   (via the parent's value)
padding:   1.5em -> 36px   (via its own font-size)
line-height: 1.5em -> 36px

--- what a percentage resolves against ---
width        %50 -> 320.0px   (reference: containing width = 640)
padding-top  %50 -> 320.0px   (reference: containing WIDTH = 640)
margin-left  %50 -> 320.0px   (reference: containing width = 640)
height       %50 -> 200.0px   (reference: containing height = 400)
font-size    %50 -> 8.0px   (reference: parent font-size = 16)
line-height  %50 -> 8.0px   (reference: own font-size = 16)

--- absolute unit conversions (1in = 96px) ---
1px  = 1.0000px
1in  = 96.0000px
1cm  = 37.7953px
1mm  = 3.7795px
1q   = 0.9449px
1pt  = 1.3333px
1pc  = 16.0000px

--- ch and ex (in an example font) ---
font-size=16px, 1ch=8px, 1ex=8.32px
max-width: 70ch -> 560px
```

## The em Chain

The first two blocks show the difference at the center of this
lesson. On the same tree, a `font-size: 1.25` declaration got written
at every generation. Written with `em`, the value got multiplied **on
top of the previous one** at every generation, and passed 39 pixels
by the fifth generation. Written with `rem`, every generation used
the root element's size, and the value stayed fixed at 20 pixels.

`em` — depends on the element's **own computed** `font-size` value.
When used in the `font-size` property itself, the **parent's** value
gets used, because the element's own value has not been computed yet.
The chained growth comes from this.

`rem` — always depends on the **root element's** `font-size` value.
There is no chain.

The third block shows the distinction within the same element.
`font-size: 1.5em` gave 24 pixels over the parent's 16 pixels. On the
same element, `padding: 1.5em` gave 36 pixels over the element's
**own** 24 pixels. The two declarations carry the same number but
resolve against different bases.

This behavior of the `em` unit is not a flaw, it is the tool for
component scaling: when a button's padding gets written in `em`, the
padding scales along with it once the button's font size changes.
Written in `rem`, it would stay fixed.

## A Percentage's Reference Varies by Property

The fourth block breaks a common assumption. A percentage does not
always resolve against the same dimension.

`width`, `margin`, and — note — every `padding` value, **including
the vertical padding**, resolves against the containing block's
**inline dimension**. The writing `padding-top: 50%` gives half the
containing block's width, not its height.

This rule, which looks strange, is the basis for a pattern: giving a
box an aspect ratio. Because `padding-top` depends on width, a box
whose height is proportional to its width can get built this way. The
`aspect-ratio` property does the same job directly, and is the
preferred way today.

`height` resolves against the containing block's height, `font-size`
against the parent's font size, `line-height` against the element's
**own** font size.

## The Cost of Writing Pixels

The fifth block gives the absolute units' fixed ratios. These ratios
are calculable, but the real dimension of the decision lies
elsewhere.

A user can change the default font size in their browser. This
setting affects the root element's `font-size` value. The consequence
is this:

- Dimensions written with `rem` and `em` grow **together** with this
  setting.
- Dimensions written with `px` **do not change**.

A page with font size written in pixels ignores the user's reading
setting. Zooming still works, but zooming enlarges the whole page —
what the user wants is usually just the text getting bigger.

The applicable rule: **font size, spacing, and box dimensions get
written in `rem` or `em`.** `px` gets used only for dimensions that
genuinely need to stay fixed: thin borders, one-pixel dividers,
shadow offsets.

Writing a fixed pixel value into the root element's `font-size`
brings the same problem back; this value either does not get written
at all, or gets written with a percentage.

## Other Font-Dependent Units

The last block defines the `ch` unit: the width of the "0" character
in a font. The `max-width: 70ch` declaration written in the Box
Sizing lesson finds its counterpart here — 560 pixels at the
example's ratio.

`ex` corresponds to lowercase letter height, `cap` to uppercase
letter height, `lh` to the element's line height. Their value depends
on the chosen font; the 0.5 and 0.52 ratios in the example are sample
values.

The `lh` unit is useful for building a spacing scale: writing
`margin-block-end: 1lh` makes the gap exactly one line height, and
the text's baseline rhythm gets preserved.

## Viewport Units

`1vw` is one percent of the viewport's width, `1vh` one percent of
its height. `vmin` and `vmax` use the smaller and the larger of the
two.

There is a distinction in the height units. In some environments, the
browser interface hides and shows on scroll, and the viewport's
height changes. Three families get defined for this: `svh` is the
smallest, `lvh` the largest, `dvh` uses the current height. Writing
`100vh` can produce a box that overflows while the interface is
visible; `100svh` prevents this.

Writing font size directly in `vw` is problematic: when the user
zooms in, the viewport width gets measured with the zoom applied, so
the text does not grow. For scaling typography, the viewport unit
does not get used alone, but together with a lower bound — the tool
for this gets taken up in the next lesson.

```css
/* station.css — step 24: unit choice */
:root { font-size: 100%; }          /* preserve the user's setting */

main { max-width: 70ch; padding-inline: 1rem; }

h1 { font-size: 2.4414rem; margin-block-end: 0.5em; }

.status-badge { padding-block: 0.25em; padding-inline: 0.75em; }

.measurement-table th,
.measurement-table td {
  padding-block: 0.5rem;
  padding-inline: 0.75rem;
  border-block-end: 1px solid var(--line);
}

.card { box-shadow: 0 1px 2px rgb(28 39 51 / 0.06); }
```

The badge's padding got written in `em`: when the badge gets used at
a small font size, the padding shrinks too. The cell padding got
written in `rem`: the table's rhythm has to stay independent of the
cell content's size.

The heading's bottom margin got written in `em` and got tied to the
heading's own size; large headings get large spacing.

The border and shadow offset stayed in `px`: these are dimensions
that blur when scaled and are meant to stay fixed.

## Summary

- `em` depends on the element's own computed font size; when used in
  the `font-size` property, it resolves against the parent's value
  and grows in a chain by getting multiplied again at every
  generation.
- `rem` always depends on the root element's font size; there is no
  chain, and the value is unaffected by the tree's depth.
- A percentage's reference varies by property: `width` and **every**
  `padding` value resolve against the containing block's inline
  dimension, `height` against the containing block's block dimension,
  `line-height` against the element's own font size.
- Dimensions written in `px` do not grow with the user's font-size
  setting; font, spacing, and box dimensions get written in `rem` or
  `em`, `px` gets reserved for fine details that need to stay fixed.
- Viewport height units have three families; `svh` is the smallest,
  `lvh` the largest, `dvh` uses the current height.

## Next Step

Units are defined, but individual fixed values still get written one
by one. A dimension may need to stay between two values, the smaller
of two dimensions may need to get chosen, or one value may need to get
subtracted from another. The next lesson — the course's last lesson —
defines the functions that do these calculations and wraps up the
style sheet the course built.
