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; }
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> ``` ```css .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.
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.
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.
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.
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: blue; color: var(--primaryColor); }
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.
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.
Leveraging CSS Variable scope improves the size, specificity, and 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.
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.
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.
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. 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 and mobile apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
3 Replies to "CSS variables: Scoping"
Some serious issues in your article. First the “root” class has one colon, e.g. “:root”, second, your fallback sample, the “color: var(–primaryColor);” need to be after the “color: blue;” or else it won’t work
Thanks for catching, we’ve updated the post
Wow, thanks for that.