Learn the basics of the CSS variables use, scope, restraints, and interaction with JavaScript, including interactive examples.
Have your Codepen blank and ready so you can test it for yourself.
Almost all programming languages support the use of variables, but not CSS. Initially, it did not support them. Until now.
Really, rather than “variables,” they are actually CSS custom properties. (Yes, the title is technically wrong, but catchier :)
CSS Is Messy
Anyone who has had to work with CSS knows that it is difficult to keep the code neat.
For example, using the same values in different rules is a pain to maintain and it’s error prone (i.e., typo). Many times we prefer to trust our text editor’s old “find-and-replace,” and that may work for a while. But the larger the project, the easier it is to make changes that mess up something else.
But What About…?
You might be thinking, “variables in CSS? What for? if I already have them via my favorite preprocessor”
And yes, we have tools to help us with these repetitive tasks. Preprocessors such as SASS and Stylus support variables. This makes you more efficient when developing and maintaining a large-scale application. However, its downside is the difficulty of being able to make changes during runtime due to its nature.
Preprocessors “compile” into standard CSS code. An alternative would be to generate code dynamically as required. However, this would end up being very complicated and slow.
Reasons to Use Variables in CSS
- Legible code
- Making changes in large projects is much easier
- Avoid typos
- The ability to make changes at runtime
What Are these Custom CSS Properties?
Basically, these two features have been added,
- The ability to assign arbitrary values to properties with fully customizable names
- The
var()
function to obtain the value of these properties.
Here’s a simple example,
1
2
3
4
5
6
7
|
:root {
--brand-color: #666;
}
#main {
color: var(--brand-color);
}
|
Where --brand-color
is a property that we have defined with the value #666
. And var()
is the function that allows us to access the value that we previously defined, resulting in color: #666;
.
Syntax
The syntax is very simple: all properties must start with the double hyphen (--
).
Properties are case-sensitive, so --brand-color
is different from --Brand-color
which is different from --Brand-Color
.
I know that you must be thinking that the syntax is unattractive (and you’d be right). But having the double hyphen ensures retro-compatibility with browsers that do not yet support these functions.
Cascades/Scoping
It is possible to define properties either as “local” or “global.” The variables are accessible within the scope of the elements where they were defined. All child elements can access the parents’ properties, but not the other way around.
Custom properties follow the same rules as cascading styles. We can define the same property at different levels of specificity,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<style>
:root { --text-color: green; }
div { --text-color: blue; }
.error { --text-color: network; }
* {
color: var(--text-color);
}
</style>
<p> I'm green because I got my color from root</p>
<div>I got blue from div selector</div>
<div class="error">
I'm red because of the .error rule
<p>Another red here because of inheritance!</p>
</div>
|
Note that :root
is the top most element (equivalent to the global scope). The same property name can be assigned a different value within a child element. Interesting, isn’t it?
We can also change values within media queries,
1
2
3
4
5
6
7
8
9
10
11
|
:root {
--gutter: 10px 0;
}
main {
padding: var(--gutter);
}
@media (min-width: 600px) {
:root {
--gutter: 0 0 0 16px;
}
}
|
Most well-known CSS preprocessors won’t let you define variables within media queries. Wow!
It is even possible to define properties from other existing ones,
1
2
3
4
|
:root {
--brand-color: red;
--header-text-color: var(--brand-color);
}
|
⚠️ Note that this doesn’t work with Microsoft Edge 15 due to a known bug.
Although not highly recommended, you can define properties in style tags, <html style = "--color: red;">
.
var()
According to MDN, the var()
function has the following syntax,
1
|
var( <custom-property-name>[, <declaration-value>]? )
|
Where custom-property-name
is the name of the property that you are defining. If this is invalid or does not exist, declaration-value
will be used instead.
We can state more than one comma-separated values, just as with font-family.
Be careful with your commas. For example, to specify more two values for padding, do it like this,
1
2
3
|
.foo {
padding: var(--gutter, 10px 0 0 5px);
}
|
--gutter
is the primary value, and “10px 0 0 5px
” is used if --gutter
is not valid or non-existent.
Limitations
The var()
function does not support string interpolation or concatenation.
As opposed to current preprocessor, the var()
function cannot be used to define property names.
If you want to go in more depth, you can check out the CSS syntax in detail.
Circular Dependencies
It is possible to have dependencies between properties as in the following example,
1
2
3
4
|
:root {
--main-color: #c06;
--accent-background: linear-gradient(to top, var(--main-color), white);
}
|
However circular dependencies are not allowed. For example,
1
2
3
4
5
6
|
:root {
--one: #c06;
--two: #ccc;
--one: calc(var(--two) + 20px);
--two: calc(var(--one) - 20px);
}
|
In this case, both –one and –two would be considered as invalid. They initial values are not overwritten and remain the same., so they would have their initial value instead of the specified value.
Building Values with calc()
The function calc()
is used to perform calculations and determine CSS values. It is supported by all modern browsers.
You can combine it with var()
to build values on the fly,
1
2
3
4
|
header {
--gutter: 20;
padding: calc(var(--gutter) * 1px);
}
|
In this case, --gutter
is defined as a single token with the numeric value 20, a numeric. But padding requires a unit as well (e.g., px). Since you can’t concatenate strings, you can instead multiply by “1px” in order to have a syntactically correct value.
Building with JavaScript
To get the value of a custom property in JavaScript, use the getPropertyValue()
method of the CSSStyleDeclaration
object.
1
2
3
4
5
6
7
8
9
10
11
12
|
<style>
:root {--brand-color: cyan; }
p { color: var(--brand-color); }
</style>
<p>This text is cyan</p>
<script>
const styles = getComputedStyle(document.documentElement);
const colorValue = styles.getPropertyValue('--brand-color');
// colorValue = 'cyan';
</script>
|
On the other hand, to define a given value, we use the setProperty()
method also of the CSSStyleDeclaration
object.
1
2
3
4
5
6
7
8
9
10
|
<style>
:root {--brand-color: cyan; }
p { color: var(--brand-color); }
</style>
<p>This text is red</ p>
<script>
document.documentElement.style.setProperty ('--brand-color', 'red');
</script>
|
You can also define property values from the value of others using our old friend var()
within setProperty()
?.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<style>
:root {
--brand-color: cyan;
--secondary-color: yellow;
}
</style>
p { color: var (--brand-color); }
<p>I'm yellow!</p>
<script>
document.documentElement.style.setProperty ('--brand-color', 'var (--secondary-color)');
</script>
|
Current Support
Custom properties are supported by the majority of current browsers,
Microsoft Edge 15 has issues. The following bugs are detected,
- Nested calculations are ignored
- Animations with custom properties can break the browser
- You can not use these properties in pseudo elements
Demo
Here you have another demo, a little more complete, to explore some of the possibilities using custom properties in conjunction with other features,