Menu

CSS Variables: Custom Properties, :root and var()

CSS variables (custom properties) store a value once and reuse it with var(). Learn how to declare them in :root, scope and override them, give fallbacks, switch themes with JavaScript, and avoid the invalid-value trap.

This page includes runnable editors - edit, run, and see output instantly.

Declaring and using a CSS variable

A CSS variable, officially a custom property, is a property whose name starts with --. Declare it once, usually on :root so the whole page inherits it, and read it anywhere with var().

Edit --brand to #16a34a and the card border and the button change together. That is the whole point: one value, one place to change it. Variables also combine with calc(), as the button's side padding shows.

Syntax

:root {
  --name: value;         /* declare */
}

.element {
  color: var(--name);                 /* use */
  color: var(--name, #111827);        /* use, with a fallback */
  padding: calc(var(--space) * 2);    /* compute with it */
}
RuleDetail
NameStarts with --, then letters, digits, - or _. Case sensitive: --Brand is not --brand.
ValueAlmost anything: a color, a length, a list, a string. It is not checked until it is used.
Where to declareAny rule. :root for globals, a component's selector for local values.
InheritanceCustom properties inherit, so a child sees its ancestors' values.
Where var() worksIn property values only, not in selectors, property names or @media conditions.

:root matches the <html> element. It is used instead of html because it has the specificity of a class, and by convention it signals "global settings".

Scope: override a variable per component

Because variables inherit, redeclaring one on an element changes it for that element and everything inside it. The rule that uses the variable stays the same.

The variants set only a variable, never the border itself. Adding a fourth variant is one line.

Fallback values

var(--name, fallback) uses the fallback when the variable is not defined on the element or any ancestor. Everything after the first comma is the fallback, so it can contain commas itself (var(--font, Georgia, serif)), and it can be another var().

Changing variables with JavaScript: a theme switch

JavaScript can set a variable on any element with style.setProperty(). Set it on document.documentElement (the <html> element) and the whole page follows. The more common pattern toggles a class that redefines the variables:

Read a variable back with getComputedStyle(element).getPropertyValue('--text'). Note that setProperty writes an inline style on <html>, which beats the .dark class, so after pressing the second button the text color stays put in both themes until the inline value is removed with root.style.removeProperty('--text').

Passing values from HTML

A variable in a style attribute is the cleanest way to hand per-element data to a stylesheet. The design stays in CSS; the HTML only supplies a number.

calc(var(--value) * 1%) turns the plain number into a percentage. Variables can also change inside a media query: @media (min-width: 900px) { :root { --space: 20px; } } makes every spacing that reads --space grow on wide screens.

The invalid value trap

The browser cannot know in advance whether a variable holds a valid value for the property that uses it. When it does not, the property does not fall back to the previous declaration in the rule. It becomes invalid at computed-value time and behaves as if set to unset: inherited properties take the parent's value, others take their initial value.

The text inherits 16px from <body>, and border-width returns to its initial value, medium, which browsers draw as 3px. Without variables, an invalid declaration is dropped and the one before it survives; with variables, it is too late for that. A fallback does not help when the variable is defined but wrong: the fallback is only for an undefined variable.

CSS variables vs Sass variables

CSS variablesSass variables
Syntax--brand: #2563eb; and var(--brand)$brand: #2563eb;
When resolvedIn the browser, liveAt build time, gone from the output
Change per elementYes, by inheritanceNo
Change with a class or media queryYesNo
Change from JavaScriptYesNo
Use in selectors or media conditionsNoYes

The two work together: many projects use Sass for build-time logic and emit CSS variables for anything themeable.

Common mistakes

  • Forgetting the two hyphens. var(brand) and --brand declared as -brand do not work.
  • Using a variable in a media query. @media (min-width: var(--bp)) is not valid. Media conditions need literal values.
  • Expecting the earlier declaration to survive an invalid variable. It does not; see the trap above.
  • Mixing units wrongly. --gap: 10; then margin: var(--gap)px does not produce 10px. Use calc(var(--gap) * 1px) or store 10px.
  • Declaring on the wrong element. A variable declared on .card is not visible to its siblings or parent; declare shared values on :root.

Frequently Asked Questions

How do you declare a CSS variable?

Write a property whose name starts with two hyphens inside a rule, usually :root for a global value: :root { --brand: #2563eb; }. Read it anywhere with var(--brand), for example color: var(--brand);.

What is :root in CSS?

:root is a pseudo-class that matches the document's root element, which is <html> in an HTML page. Variables declared there are inherited by every element. It has the specificity of a class, so it beats a plain html selector.

How do you set a default value for a CSS variable?

Pass a second argument to var(): color: var(--accent, #2563eb);. The fallback is used when --accent is not defined on the element or its ancestors. It is not used when the variable is defined with a value that is invalid for the property.

How do you change a CSS variable with JavaScript?

Call setProperty on an element's style: document.documentElement.style.setProperty('--brand', '#16a34a'). Every rule using var(--brand) updates at once. Read the current value with getComputedStyle(el).getPropertyValue('--brand').

What is the difference between CSS variables and Sass variables?

Sass variables are replaced with their values when the stylesheet is compiled, so they cannot change in the browser. CSS variables live in the browser: they inherit, can differ per element, respond to media queries and classes, and can be changed from JavaScript.

Are CSS variable names case sensitive?

Yes. --Brand and --brand are two different variables, unlike ordinary property names such as color, which are case insensitive.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED