loading...
Cover image for CSS variables: scoping
LogRocket

CSS variables: scoping

bnevilleoneill profile image Brian Neville-O'Neill Originally published at blog.logrocket.com on ・7 min read

Written by Chidume Nnamdi✏️

We can declare variables in CSS in the same way we declare variables in other programming languages.

We declare a variable in CSS assigned with a value. The variable can then be consumed in CSS rulesets.

In JavaScript, for example, we declare variables using the let, var and const keywords:

let nine = 9;
var ten = 10;
var eight = 8;

Then, we consume them by referring to their variable names:

>> nine
9
>>  ten
10
>> eight 
8

We can also do this in CSS. To declare a variable in CSS, we use this format:

--varName

In CSS, it’ll look like this:

body {
    --mainColor: limegreen;
}

Let’s say we declare a variable with the name --mainColor and a value of limegreen. This variable holds a color name.

To consume variables in CSS, we use var():

var(--varName);

var will retrieve the value of the variable passed to it, and replace itself with the value:

body {
    --mainColor: limegreen;
}

div {
    color: var(--mainColor);
}

Here, div consumes the mainColor variable using the var function. The var will retrieve the value limegreen from --mainColor and replace itself with limegreen, the value of the --mainColor variable.

So, the color of the text node in the div element will be lime green. In other words, the CSS code would translate to this:

body {
    --mainColor: limegreen;
}

div {
    color: limegreen;
}

Scoping

The place in the CSS hierarchy where you declare a CSS variable will determine its level of visibility throughout the lower levels of the hierarchy.

A CSS variable used throughout the entire page is declared in the ::root pseudo selector, or in the html selector.

This is because all the elements on our page are enclosed in one HTML element, so CSS variables declared in the HTML element or in its ::root selector will be visible for consumption from its children.

::root is a pseudo selector attached to the root of the HTML element in a document. In an RSS document, the ::root element is attached to the RSS element.

Generally, a CSS variable is only visible to child elements of the parent element it is declared in.

Now, we have this:

<body>
    <div>Div 1</div>
    <div>Div 2</div>
</body>

The body element is parent to its child elements div, Div 1, and Div 2.

So this is feasible:

body {
    --bgColor: limegreen;
}

div {
    background: var(--bgColor);
}

The CSS variable bgColor is declared in the body element and consumed in a div to set the background of div elements to lime green.

This would be visible to our two div elements because they are child elements to the body element, which is where the CSS variable it consumed is declared.

Now, if the reverse was true:

body {
    background: var(--bgColor);
}

div {
    --bgColor: limegreen;
}

<body>
    <div>Div 1</div>
    <div>Div 2</div>
</body>

The --bgColor variable is declared in the div element and consumed in the body element. Now, the body element is above the div element, so the --bgColor variable won’t be visible to the body element. As a result, the background of the body element won’t turn to lime green.

The --bgColor variable in this case will be visible to elements beneath the div elements:

body {
    background: var(--bgColor);
}

div {
    --bgColor: limegreen;
}

p {
    background: var(--bgColor);
}

<body>
    <div>Div 1
        <p>Paragraph 1</p>
    </div>
    <div>Div 2
        <p>Paragraph 2</p>
    </div>
</body>

The --bgColor variable will be visible to the p elements because they are child elements of div.

We can see here that the visibility of CSS variables depends on a parent-child relationship. A child element is within the scope of its parent element, so it can use the CSS variable declarations in the parent scope.

Theming is done with CSS variables, and mostly the theming propagates the entire DOM tree, so the CSS variables are usually set in the html element or in the ::root element. This is because no element is placed outside the HTML element:

::root {
    --bgColor: lightcoral;
    --mainColor: limegreen;
    --borderColor: seagreen;
}

In rare cases when we want to theme a portion of the DOM tree or to theme a DOM branch using CSS variables, we’ll set the CSS variables in the root element of the DOM branch so it will propagate down the branch tree.

<html>
    <body>
        <div class="branch">
            <p>
                Paragraph 1
            </p>
        </div>
        <p>
            Paragraph 2
        </p>
    </body>
</html>
.branch {
    --brBgColor: palevioletred;
    --brMainColor: blueviolet;
}

p {
    background: var(--brBgColor);
    color: var(--brMainColor);
}

The CSS variables declared in the div.branch will be visible to the p element Paragraph 1, because it is a child element of the div with class name branch. The background color and text color will be painted palevioletred and blueviolet colors, respectively. The p element Paragraph 2 will not be affected with the styling because the CSS variables --brBgColor --brMainColor will not be visible to it.

This is because it is not a child element of the div.branch.

So, this is scoping at work in CSS variables.

Global Scope

CSS variables declared in the ::root selector are said to be in the Global scope. This means that they can be accessed anywhere in the CSSOM.

Why does it work?

Like we learned earlier, the ::root selector is attached at the root of the document. All elements in the web document are under the root document, so CSS variables declared in the ::root will cascade down to all levels if the document. Alternatively, the declared CSS variables will be visible to all elements in the document.

Local Scope

Just like in the ::root selector, CSS variables can also be declared in all levels of the CSSOM hierarchy or a particular selector.

Now, CSS variables declared in these levels or in a selector are only visible or scoped locally to the selector and its child nodes.

Hoisting

CSS Variables are hoisted and they are moved on the top of the CSSOM before rendering the styles of respective HTML elements in the browser.

Just like in JavaScript, CSS Variables can be hoisted. This means that CSS variables can be used before they are declared.

var num2 = 90

var add = num1 + num2

var num1 = 10

log(add)

In the above example, the num1 variable is being used before it is declared. That means that num1 was hoisted. Upon running the code, add will log 100. Despite being used first before declaration, JavaScript was able to get the value and perform the operation.

The same is also true in CSS variables:

body {
    background-color: var(--bgColor);
}

::root {
    --bgColor: rgb(221, 221, 221);
}

As you can see, the CSS variable --bgColor was used before it was declared in the ::root pseudo-selector. And the code works perfectly fine!

So, CSS variables can be accessed first and declared later. This makes CSS variables a very powerful feature.

Checking for Support

CSS variables are widely supported in major browsers, though support may be lacking in older versions of Chrome and Firefox. Support for CSS variables in IE and Edge are presently underway.

Because not all browsers support CSS variables, we can detect CSS variable feature support using the @supports.

@supports(--bgColor: rgb(221, 221, 221)) {

}

The second option is to set a fallback value:

::root {
    --primaryColor: blue;
}

button {
    color: var(--primaryColor);
    color: blue;
}

We set up a --primaryColor variable in the ::root selector with color blue. However, we’re not sure the browser our CSS runs on supports CSS variables. To make the code work, we added a fallback value in the button selector. This makes sure the button uses our primary color in browsers that don’t support CSS variables.

Advantages of using CSS variables

Theming

CSS Variables scoping improves the way we add and modify themes in our CSS. With CSS variables, theming in CSS won’t require extra stylesheets with different themes. Instead, all you need to do is update the CSS variables.

Styling

Leveraging CSS Variable scope improves the size, specificity, & semantics of our stylesheets

Let’s say we have a button:

button {
    padding: 10px 5px;
}

With different button styles:

.btn-danger {
    background-color: orange;
}

.btn-success {
    background-color: lightblue;
}

With CSS variables, we don’t have to define background-color in every button style.

button {
    --btnBgColor: blue;
    padding: 10px 5px;
    background-color: var(--btnBgColor);
}

.btn-danger {
    --btnBgColor: orange;
}

.btn-success {
    --btnBgColor: lightblue;
}

We simply assign the --btnBgColor new values in every button style. We no longer need to override the base styles.

See? CSS Variables are very powerful, and the scoping feature makes them an ideal tool for clean, modular design systems.

Conclusion

We just treated a potential bug in CSS today.

Just as we have scoping in JavaScript and other languages, the same is true in CSS variables. We have seen one major application of CSS variables: theming. There are lots more it can offer us.

If you have any questions regarding this or anything I should add, correct or remove, feel free to comment, email, or DM me.


Is your frontend hogging your users' CPU?

As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.

Alt Text

LogRocket is like a DVR for web apps, recording everything that happens in your web app or site. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.

Modernize how you debug web apps — Start monitoring for free.


The post CSS variables: scoping appeared first on LogRocket Blog.

Posted on by:

bnevilleoneill profile

Brian Neville-O'Neill

@bnevilleoneill

Director content @LogRocket. I didn't write the post you just read. To find out who did, click the link directly above my name.

LogRocket

LogRocket's thoughts on all things frontend.

Discussion

markdown guide