Using CSS custom properties (variables)
Custom properties (sometimes referred to as CSS variables or cascading variables) are entities defined by CSS authors that represent specific values to be reused throughout a document. They are set using the @property
at-rule or by custom property syntax (e.g., --primary-color: blue;
). Custom properties are accessed using the CSS var()
function (e.g., color: var(--primary-color);
).
Complex websites have very large amounts of CSS, and this often results in a lot of repeated CSS values. For example, it's common to see the same color used in hundreds of different places in stylesheets. Changing a color that's been duplicated in many places requires a search and replace across all rules and CSS files. Custom properties allow a value to be defined in one place, then referenced in multiple other places so that it's easier to work with. Another benefit is readability and semantics. For example, --main-text-color
is easier to understand than the hexadecimal color #00ff00
, especially if the color is used in different contexts.
Custom properties defined using two dashes (--
) are subject to the cascade and inherit their value from their parent.
The @property
at-rule allows more control over the custom property and lets you specify whether it inherits its value from a parent, what the initial value is, and the type constraints that should apply.
Note: Variables do not work inside media queries and container queries.
You can use the var()
function in any part of a value in any property on an element.
You cannot use var()
for property names, selectors, or anything aside from property values, which means you can't use it in a media query or container query.
Declaring custom properties
In CSS, you can declare a custom property using two dashes as a prefix for the property name, or by using the @property
at-rule.
The following sections describe how to use these two methods.
Using a prefix of two dashes (--
)
A custom property prefixed with two dashes begins with --
, followed by the property name (e.g., --my-property
), and a property value that can be any valid CSS value.
Like any other property, this is written inside a ruleset.
The following example shows how to create a custom property --main-bg-color
and uses a <named-color>
value of brown
:
section {
--main-bg-color: brown;
}
The selector given to the ruleset (<section>
elements in the example above) defines the scope in which the custom property can be used.
For this reason, a common practice is to define custom properties on the :root
pseudo-class, so that it can be referenced globally:
:root {
--main-bg-color: brown;
}
This doesn't always have to be the case: you maybe have a good reason for limiting the scope of your custom properties.
Note: Custom property names are case sensitive — --my-color
will be treated as a separate custom property to --My-color
.
Using the @property
at-rule
The @property
at-rule allows you to be more expressive with the definition of a custom property with the ability to associate a type with the property, set default values, and control inheritance.
The following example creates a custom property called --logo-color
which expects a <color>
:
@property --logo-color {
syntax: "<color>";
inherits: false;
initial-value: #c0ffee;
}
If you want to define or work with custom properties in JavaScript instead of directly in CSS, there is a corresponding API for this purpose. You can read about how this work in the CSS Properties and Values API page.
Referencing custom properties with var()
Regardless of which method you choose to define a custom property, you use them by referencing the property in a var()
function in place of a standard property value:
details {
background-color: var(--main-bg-color);
}
First steps with custom properties
Let's start with some HTML that we would like to apply some styles to.
There is a <div>
that acts as a container that includes some child elements, some with nested elements:
<div class="container">
<div class="one">
<p>One</p>
</div>
<div class="two">
<p>Two</p>
<div class="three">
<p>Three</p>
</div>
</div>
<input class="four" placeholder="Four" />
<textarea class="five">Five</textarea>
</div>
We will use the following CSS to style a few different elements based on their classes (some layout rules are not shown below so we can focus on colors).
Depending on their classes, we're giving elements cornflowerblue
or aquamarine
background colors:
/* For each class, set some colors */
.one {
background-color: cornflowerblue;
}
.two {
color: black;
background-color: aquamarine;
}
.three {
background-color: cornflowerblue;
}
.four {
background-color: cornflowerblue;
}
.five {
background-color: cornflowerblue;
}
This produces the following result:
There's an opportunity to use custom properties to replace repetitive values across these rules.
After defining --main-bg-color
in the .container
scope and referencing its value in multiple places, the updated styles look like this:
/* Define --main-bg-color here */
.container {
--main-bg-color: cornflowerblue;
}
/* For each class, set some colors */
.one {
background-color: var(--main-bg-color);
}
.two {
color: black;
background-color: aquamarine;
}
.three {
background-color: var(--main-bg-color);
}
.four {
background-color: var(--main-bg-color);
}
.five {
background-color: var(--main-bg-color);
}
Using the :root pseudo-class
For some CSS declarations, it is possible to declare this higher in the cascade and let CSS inheritance solve this problem. For non-trivial projects, this is not always possible. By declaring a custom property on the :root
pseudo-class and using it where needed throughout the document, a CSS author can reduce the need for repetition:
/* Define --main-bg-color here */
:root {
--main-bg-color: cornflowerblue;
}
/* For each class, set some colors */
.one {
background-color: var(--main-bg-color);
}
.two {
color: black;
background-color: aquamarine;
}
.three {
background-color: var(--main-bg-color);
}
.four {
background-color: var(--main-bg-color);
}
.five {
background-color: var(--main-bg-color);
}
This leads to the same result as the previous example, yet allows for one canonical declaration of the desired property value (--main-bg-color: cornflowerblue;
), which is very useful if you want to change the value across the entire project later.
Inheritance of custom properties
A custom property defined using two dashes --
instead of @property
always inherits the value of its parent.
This is demonstrated in the following example:
<div class="one">
<p>One</p>
<div class="two">
<p>Two</p>
<div class="three"><p>Three</p></div>
<div class="four"><p>Four</p></div>
</div>
</div>
div {
background-color: var(--box-color);
}
.two {
--box-color: cornflowerblue;
}
.three {
--box-color: aquamarine;
}
The results of var(--box-color)
depending on inheritance are as follows:
class="one"
: invalid value, which is the default value of a custom property defined in this wayclass="two"
:cornflowerblue
class="three"
:aquamarine
class="four"
:cornflowerblue
(inherited from its parent)
One aspect of custom properties that the examples above demonstrate is that they don't behave exactly like variables in other programming languages. The value is computed where it is needed, not stored and reused in other places of a stylesheet. For instance, you cannot set a property's value and expect to retrieve the value in a sibling's descendant's rule. The property is only set for the matching selector and its descendants.
Using @property
to control inheritance
The @property
at-rule lets you explicitly state whether the property inherits or not.
The following example creates a custom property using the @property
at-rule.
Inheritance is disabled, there's a <color>
data type defined, and an initial value of cornflowerblue
.
The parent element sets --box-color
to a value of green
and uses --box-color
as a value for its background color.
The child element also uses background-color: var(--box-color)
, and we would expect it to have the color green
if inheritance was enabled (or if it was defined using the double dash syntax).
<div class="parent">
<p>Parent element</p>
<div class="child">
<p>Child element with inheritance disabled for --box-color.</p>
</div>
</div>
@property --box-color {
syntax: "<color>";
inherits: false;
initial-value: cornflowerblue;
}
.parent {
--box-color: green;
background-color: var(--box-color);
}
.child {
width: 80%;
height: 40%;
background-color: var(--box-color);
}
Because inherits: false;
is set in the at-rule, and a value for the --box-color
property is not declared within the .child
scope, the initial value of cornflowerblue
is used instead of green
that would have been inherited from the parent:
Custom property fallback values
You can define fallback values for custom properties using the var()
function, and the initial-value
of the @property
at-rule.
Note: Fallback values aren't used to fix compatibility issues for when CSS custom properties are not supported, as the fallback value won't help in this case. Fallbacks cover the case where the browser supports CSS custom properties and is able to use a different value if the desired variable isn't defined yet or has an invalid value.
Defining fallbacks in the var()
function
Using the var()
function, you can define multiple fallback values when the given variable is not yet defined; this can be useful when working with Custom Elements and Shadow DOM.
The first argument to the function is the name of the custom property. The second argument to the function is an optional fallback value, which is used as the substitution value when the referenced custom property is invalid. The function accepts two parameters, assigning everything following the first comma as the second parameter. If the second parameter is invalid, the fallback will fail. For example:
.one {
/* Red if --my-var is not defined */
color: var(--my-var, red);
}
.two {
/* pink if --my-var and --my-background are not defined */
color: var(--my-var, var(--my-background, pink));
}
.three {
/* Invalid: "--my-background, pink" */
color: var(--my-var, --my-background, pink);
}
Including a custom property as a fallback, as seen in the second example above (var(--my-var, var(--my-background, pink))
), is the correct way to provide more than one fallback with var()
.
You should be aware of the performance impact of this method, however, as it takes more time to parse through the nested variables.
Note: The syntax of the fallback, like that of custom properties, allows commas. For example, var(--foo, red, blue)
defines a fallback of red, blue
— anything between the first comma and the end of the function is considered a fallback value.
Fallbacks using the @property
initial value
Aside from using var()
, the initial-value
defined in the @property
at-rule can be used as a fallback mechanism.
In fact, we've already seen this in the @property
inheritance section.
The following example sets an initial value of --box-color
to cornflowerblue
using the @property
at-rule.
In the ruleset following the at-rule, we want to set --box-color
to aquamarine
, but there's a typo in the value name.
The same is true for the third <div>
where we've used 2rem
for the custom property that's expecting a valid <color>
value.
Both 2rem
and aqumarine
are invalid color values, so the initial value of cornflowerblue
is applied:
@property --box-color {
syntax: "<color>";
initial-value: cornflowerblue;
inherits: false;
}
.one {
--box-color: aquamarine;
background-color: var(--box-color);
}
.two {
--box-color: aqumarine;
background-color: var(--box-color);
}
.three {
--box-color: 2rem;
background-color: var(--box-color);
}
Invalid custom properties
Each CSS property can be assigned a defined set of values. If you try to assign a value to a property that is outside its set of valid values, it's considered invalid.
When the browser encounters an invalid value for a regular CSS property (for example, a value of 16px
for the color
property), it discards the declaration, and elements are assigned the values that they would have had if the declaration did not exist.
In the following example, we see what happens when a regular CSS declaration is invalid; color: 16px;
is discarded and the previous color: blue
rule is applied instead:
<p>This paragraph is initially black.</p>
p {
color: blue;
}
p {
/* oops, not a valid color */
color: 16px;
}
However, when the values of custom properties are parsed, the browser doesn't yet know where they will be used, so it must consider nearly all values as valid.
Unfortunately, these valid values can be used, via the var()
functional notation, in a context where they might not make sense.
Properties and custom variables can lead to invalid CSS statements, leading to the concept of valid at computed time.
When the browser encounters an invalid var()
substitution, then the initial or inherited value of the property is used.
This example is just like the last one, except we use a custom property.
The browser substitutes the value of --text-color
in place of var(--text-color)
, but 16px
is not a valid property value for color
.
After substitution, the property doesn't make sense., so the browser handles this situation in two steps:
- Check if the property
color
is inheritable. It is, but this<p>
doesn't have any parent with thecolor
property set. So we move on to the next step. - Set the value to its default initial value, which is black.
<p>This paragraph is initially black.</p>
:root {
--text-color: 16px;
}
p {
color: blue;
}
p {
color: var(--text-color);
}
For such cases, the @property
at-rule can prevent unexpected results by allowing to define the initial value of the property:
<p>This paragraph is initially black.</p>
@property --text-color {
syntax: "<color>";
inherits: false;
initial-value: cornflowerblue;
}
:root {
--text-color: 16px;
}
p {
color: blue;
}
p {
color: var(--text-color);
}
Values in JavaScript
To use the values of custom properties in JavaScript, it is just like standard properties.
// get variable from inline style
element.style.getPropertyValue("--my-var");
// get variable from wherever
getComputedStyle(element).getPropertyValue("--my-var");
// set variable on inline style
element.style.setProperty("--my-var", jsVar + 4);