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 */
}
| Rule | Detail |
|---|---|
| Name | Starts with --, then letters, digits, - or _. Case sensitive: --Brand is not --brand. |
| Value | Almost anything: a color, a length, a list, a string. It is not checked until it is used. |
| Where to declare | Any rule. :root for globals, a component's selector for local values. |
| Inheritance | Custom properties inherit, so a child sees its ancestors' values. |
Where var() works | In 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 variables | Sass variables | |
|---|---|---|
| Syntax | --brand: #2563eb; and var(--brand) | $brand: #2563eb; |
| When resolved | In the browser, live | At build time, gone from the output |
| Change per element | Yes, by inheritance | No |
| Change with a class or media query | Yes | No |
| Change from JavaScript | Yes | No |
| Use in selectors or media conditions | No | Yes |
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--branddeclared as-branddo 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;thenmargin: var(--gap)pxdoes not produce10px. Usecalc(var(--gap) * 1px)or store10px. - Declaring on the wrong element. A variable declared on
.cardis 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.