CSSintroevergreen

CSS custom properties

Variables that live in the cascade.

Custom properties aren't preprocessor variables β€” they're real values in the DOM that inherit, cascade, and update live. Define once with --name, read with var().

Declare with --name, read with var()

A custom property is any property whose name starts with two dashes: --accent: #e9b454. You read it back with the var() function: color: var(--accent). Names are case-sensitive, so --Accent and --accent are different properties.

The convention is to declare your shared values on :root (the <html> element) so they're available everywhere. Because custom properties inherit, a value set on :root flows down to every descendant unless something closer overrides it.

Fallbacks and the cascade

var() takes an optional second argument: a fallback used when the property is missing or invalid. var(--gap, 8px) resolves to 8px if --gap was never set. The fallback can itself be a var(), so you can chain defaults.

Custom properties obey the normal cascade. Whatever wins specificity wins for the property too β€” set --accent on :root, then re-set it on .theme-warm, and any element inside .theme-warm reading var(--accent) gets the warm value. This is what makes theming a matter of swapping a class rather than rewriting rules.

Compute with them

Because the resolved value is a real value, you can feed it into calc(): padding: calc(var(--space) * 2). Custom properties are resolved at the element where var() appears, using the value inherited there β€” so a single --space token can drive a whole spacing scale by multiplying it inline.

Try it 5 examples

Define on :root, use everywhere

HTML+CSS+JSintro
Press Run to execute

The card's panel background, ink text, amber border, title colour, and CTA all resolve from the four tokens declared once on :root. Because custom properties inherit, every descendant reads the same var() values without any of them being redeclared.