Units, custom properties and calc

Choosing between px, em, rem and viewport units, inheriting variables, registering them with @property, and doing arithmetic safely.

Pick a unit per job

A unit is a statement about what the value should follow. Sizing in rem tracks the user's font preference, in em tracks the local font size, and in dvh tracks the visible viewport on a phone with collapsing browser chrome.

UnitRelative toGood for
pxNothing — a device-independent pixelHairlines, borders, tiny fixed offsets
%The parent's corresponding dimensionFluid widths, but ambiguous height
emThe element's own font sizeSpacing that scales with its own text
remThe root font sizeType, spacing and sizing — the safe default
vw / vhThe viewportLarge decorative type; avoid for height on mobile
dvh / svhDynamic and small viewport heightFull-height heroes on phones
chThe width of a zero glyphMeasure — the line length of prose
cqwThe nearest container's widthComponent sizing inside container queries
.card { padding: 1.5rem; }

.card :is(h2, p) { margin-block: 0.75em; }  /* scales with each element's own size */

.hero { min-height: 100dvh; }

.prose { max-inline-size: 68ch; }

em compounds: a 1.5em padding on a child of a child of a 20px element is not 20px. That compounding is useful for component-relative spacing and dangerous for nesting, which is why rem is the safer default.

Custom properties are inherited values

A custom property is not a variable in the programming sense: it is an inherited CSS value that is substituted into a declaration before that declaration is parsed. Inheritance is what makes theming work, and also what makes an undefined property silently resolve to nothing.

:root { --space-3: 1rem; }

.card { padding: var(--space-3, 1rem); }  /* fallback if undefined */

@property --angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.spinner { transform: rotate(var(--angle)); }
  • Always provide a fallback with var(--name, fallback) for values that come from outside the component.
  • Unregistered custom properties are untyped strings, so a typo produces an invalid value rather than an error.
  • @property registers a type, an inheritance rule and an initial value — registered properties can be animated and transitioned.
  • Custom properties cannot be used in media query conditions, so keep breakpoints as literal values.
  • JavaScript can set them per element with element.style.setProperty("--angle", "45deg").

calc, min, max and clamp

Math functions let a layout describe a relationship instead of a fixed number, so it keeps working when a sidebar changes width or a gap gets larger.

:root {
  --gap: 1rem;
  --sidebar: 240px;
}

.layout {
  display: grid;
  gap: var(--gap);
  grid-template-columns: var(--sidebar) 1fr;
}

.content {
  max-inline-size: min(72rem, 100% - var(--sidebar) - 2 * var(--gap));
  margin-inline: auto;
}

.title { font-size: clamp(1.5rem, 1rem + 2vw, 2.5rem); }
  • calc() mixes units: 100% - 240px is legal, and is how you subtract a fixed sidebar from a fluid width.
  • min() and max() are the single-value form of clamp and read better when only one bound matters.
  • clamp() is preferred when both bounds matter, as with fluid type.
  • Nesting is fine but gets unreadable fast — hoist repeated expressions into a custom property.
⚠️
Inside calc(), + and - must be surrounded by whitespace; calc(100%-2rem) is invalid and the whole declaration is dropped silently. Multiplication and division by a plain number do not have this restriction.

FAQ

When should I use em instead of rem?
When the value should scale with the element's own text — padding inside a button, spacing inside a heading, an icon that matches the line height. For page-level rhythm and sizing, rem keeps things predictable.
Why is my custom property not applying?
Check three things in order: the spelling matches exactly, it is defined on an ancestor of the element that uses it, and the value is valid for the property it lands in. A fallback in var() hides all three mistakes.

Typography, fonts and fluid type CSS architecture, naming and design tokens

Last refreshed 2026-09-18.