Values and visual language

Custom properties and calculations

Define reusable CSS values, inherit component tokens, provide fallbacks, and combine different units with calc, min, max, and clamp.

Custom properties are variables for CSS. You define a value once, give it a name that starts with --, and read it anywhere with var().

:root {
  --space: 1rem;
  --accent: #2457d6;
}

.card {
  padding: var(--space);
  border-color: var(--card-accent, var(--accent));
}

:root is the html element, so anything defined there is available on the whole page. The second argument of var() is a fallback: if --card-accent isn’t defined, the border uses --accent instead.

They inherit

Custom properties follow normal inheritance. Define --space on body and every descendant sees it. Redefine it on one section and only that section’s descendants get the new value:

.features {
  --space: 1.5rem;
}

Every card inside .features now has more padding, and nothing else changed. This is the real power: a component reads a token, and any ancestor can adjust it. Themes, dark mode, and compact layouts all work this way.

The fallback doesn’t validate

Here’s a trap. The fallback kicks in only when the custom property is missing. It doesn’t check whether the value makes sense:

.card {
  --card-width: red;
  width: var(--card-width, 20rem);
}

--card-width exists, so the fallback is ignored, and width becomes red. That’s not a valid width, but the browser only finds out after substituting the variable, so the declaration becomes invalid at computed-value time. The result is the same as width: unset: the card gets auto width. DevTools shows the declaration with a warning icon.

Math functions

CSS can do arithmetic, and it can mix units while doing it:

h1 {
  font-size: clamp(2rem, 6vw, 4rem);
}

.layout {
  width: min(100% - 2rem, 70rem);
}

clamp(min, preferred, max) tracks the middle expression but never goes below the first or above the last. The heading grows with the viewport, from 2rem up to 4rem. That’s fluid type without a single media query.

min() picks the smallest of its arguments, max() the largest. The layout above is full width minus a gutter on narrow screens, capped at 70rem on wide ones.

calc() is the general one, for mixing units like calc(100% - 2rem). Inside min(), max(), and clamp() you can do the math directly, as above. Be careful: + and - need spaces around them, or 100%-2rem parses as one broken token.

When to make a token

Not every value deserves a variable. I create one when several places must agree on the same decision: spacing scale, brand colors, border radius. A padding used in one place stays a literal. Turn everything into tokens and you get a second stylesheet of names to remember.

Try it on the course page. Move the card padding to --space, override it on .features, and check which elements pick it up in the Computed panel. Then write padding: var(--spce, 1rem) with a typo in the name and, separately, set --space: blue. The first uses the 1rem fallback, the second throws the whole declaration away. Notice how differently the two mistakes behave.

Quick check

Result

You got of right.

Lesson completed