CSS Variables (Custom Properties)

Custom properties — usually called CSS variables — store a value once and reuse it everywhere. Change the variable, and every use updates. Unlike variables in preprocessors, they live in the browser: they cascade, inherit, can be changed by media queries and JavaScript, and power theming and dark mode.

Declaring and using a variable

Try it yourself
Result

A custom property's name starts with two hyphens and is case-sensitive. Read it with var(). Declaring variables on :root makes them available everywhere, because they inherit.

Fallback values

Try it yourself
Result

The second argument to var() is used when the variable isn't set. Fallbacks can themselves contain var().

Scoping: variables cascade

Try it yourself
Result

Redefining a variable on a more specific selector changes it for that element and its descendants. One component, many variants — each only changes the variable.

Variables in media queries and states

Try it yourself
Result

Change the values inside a media query and every rule that uses them responds. Variables can't be used in a media query condition itself, though.

Theming and dark mode

Try it yourself
Result

Lesson 46 builds a complete light/dark theme on this idea.

Changing variables with JavaScript

Try it yourself
Result

JavaScript sets a variable with element.style.setProperty("--level", value) and reads one with getComputedStyle(element).getPropertyValue("--level"). The CSS does the rest.

Typed variables with @property

Try it yourself
Result

Normally a custom property is just text, so the browser can't animate it. Registering it with @property gives it a type, which makes smooth transitions and animations of the variable possible.

Things to know

  • Variables hold any value — colours, lengths, whole shorthand values — but can't be used for property names or selectors.
  • If a variable holds an invalid value for where it's used, the property falls back to its inherited or initial value (not to an earlier declaration).
  • Name variables by purpose (--color-text, --space-lg) rather than by value (--blue), so they still make sense after a redesign.

Try it yourself

Try it yourself
Result