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,

:root { --brand-color: #666; } #main { color: var(--brand-color); } 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,

<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> 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 >

See the Pen CSS Custom Properties – sample 01 by Tony Mtz (@tonymtz) on CodePen.



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,

:root { --gutter: 10px 0; } main { padding: var(--gutter); } @media (min-width: 600px) { :root { --gutter: 0 0 0 16px; } } 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 ; } }

See the Pen CSS Custom Properties – sample 02 by Tony Mtz (@tonymtz) on CodePen

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,

:root { --brand-color: red; --header-text-color: var(--brand-color); } 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,

var( <custom-property-name>[, <declaration-value>]? ) 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,

.foo { padding: var(--gutter, 10px 0 0 5px); } 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,

:root { --main-color: #c06; --accent-background: linear-gradient(to top, var(--main-color), white); } 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,

:root { --one: #c06; --two: #ccc; --one: calc(var(--two) + 20px); --two: calc(var(--one) - 20px); } 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,

header { --gutter: 20; padding: calc(var(--gutter) * 1px); } 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.

<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> 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.

<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> 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() ?.

<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> 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,

Demo

Here you have another demo, a little more complete, to explore some of the possibilities using custom properties in conjunction with other features,

See the Pen CSS Variables demo by Tony Mtz (@tonymtz) on CodePen.



Finally…

What other cool uses can you think of? Please, let me know.