Like most programming languages, native CSS now has support for variables, and they’re here to stay.
If you know a bit of CSS, chances are you’ve heard of CSS preprocessors such as Sass and Less. You’ve probably used them in your projects regardless of your frontend framework of choice. Or maybe you haven’t and you’ve just used plain old CSS.
Either way, the major selling point of those preprocessors was that you could use variables just like you would in a programming language. You declare a variable, assign it a value, and use it across the document to make maintainability a breeze.
In this tutorial, we’ll provide a soft introduction to this concept by first demystifying CSS variables and then building two simple projects that utilize it. If you’re eager to get in on the action as soon as possible, you can find the code to both projects on CodePen (here and here).
Some basic CSS knowledge is required to follow along with this tutorial. Without further ado, let’s dive in!
To solidify our knowledge about CSS variables, we’ll build two very simple projects. The first will demonstrate how to create button variations. This concept is popular in Bootstrap, where certain elements share CSS rules that give them a default design but are differentiated by colors or other properties. The second project will be a theme-based design — specifically, a light-and-dark theme manipulated by JavaScript.
Also referred to as custom properties or cascading variables, CSS variables have myraid use cases. One of the most common is managing websites in which numerous values are similar to those in the document. This helps to reduce the friction associated with refactoring or updating your code.
To declare a variable in CSS, come up with a name for the variable, then append two hyphens (–) as the prefix.
element { --bg-color: #405580; }
The element
here refers to any valid HTML element that has access to this CSS file.
The variable name is bg-color
, and two hyphens are appended. To access a variable, use the var()
notation and pass in the variable name.
body { background-color: var(--bg-color); }
The background-color
will take the value of bg-color
that we declared earlier. More often than not, developers declare all their variables in the :root
element of the document.
:root { --primary-color: #0076c6; --blur: 10px; }
Declaring your variables here renders them globally scoped and available to the whole file.
Just like traditional CSS, CSS variables are cascading in nature — i.e., they inherit. The value of an element is inherited from its parent if no custom property is defined.
HTML:
<div class="container"> <span class="container-inner"></span> <article class="post"> <h1 class="post-title">Heading text</h1> <p class="post-content">Paragraph text</p> </article> </div>
CSS:
.container { --padding: 1rem; } .container-inner { padding: var(--padding); } .post { --padding: 1.5rem; } .post-content { padding: var(--padding); }
The padding of the .container-inner
and .post-content
classes will be different because, by default, the target selector will inherit from its nearest parent. In this case, the .post-content
selector inherits padding value from its direct parent, .post
, with the value of 1.5rem
rather than 1rem
.
When using custom properties, you might reference a custom property that isn’t defined in the document. You can specify a fallback value to be used in place of that value. The syntax for providing a fallback value is still the var()
notation. Add a list of comma-separated values after the first value. Anything after the first comma is treated as a fallback value.
:root { --light-gray: #ccc; } p { color: var(--light-grey, #f0f0f0, #f9f9f9) /* No --light-grey, so #f0f0f0 is used as a fallback value */ }
Did you notice that I misspelled the value --light-gray
? This should cause the value to be undefined, in which case the browser would use the first fallback value. If, for some reason, it can’t find the fallback value, the next value would kick in, and so on. The browser will use the initial default color if it can’t find any of the provided values.
Another use case for fallback values is when a value isn’t valid to a property that is provided.
:root { --text-danger: 16px; } body { color: var(--text-color); }
In this snippet, the --text-danger
custom property was defined with a value of 16px
, which isn’t technically wrong. But when the browser substitutes the value of --text-color
in place of var(--text-color)
, it tries to use a value of 16px
, which is not a valid property value for color in CSS.
The browser treats it as an invalid value and checks whether the color property is inheritable by a parent element. If it is, it uses it. Otherwise, it falls back to a default color (black in most browsers).
Now let’s dig into our first project.
In CSS frameworks such as Bootstrap, variables make sharing a base design across elements much easier. Take the .bg-danger
class, which turns an element’s background color to red and its own color to white. In this first project, you’ll build something similar.
Start by creating a project folder. In your terminal, you can run these commands one after the other.
# create a base project folder mkdir css-variables-pro # change directory to the created folder cd css-variables-pro # create a new folder for the first project mkdir button-variations # change directory to the new folder cd button-variations # create two files touch variations.html variations.css
This will create a project folder called css-variables-pro
. This folder will house the two projects you’ll build. Next, created a subfolder called button-variations
and two files for the first project.
Paste the following snippet in the variations.html
file you created.
<!-- css-variables-pro/button-variations/variations.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>CSS Variables - Button Variations</title> <link rel="stylesheet" href="variations.css" /> </head> <body> <section> <div class="container"> <h1 class="title">CSS Color Variations</h1> <div class="btn-group"> <button class="btn primary">Primary</button> <button class="btn secondary">Secondary</button> <button class="btn link">Link</button> <button class="btn success">Success</button> <button class="btn error">Error</button> </div> </div> </section> </body> </html>
The structure of this markup is pretty standard. Notice how each button element has two classes: the btn
class and a second class. We’ll refer to the btn
class, in this case, as the base class and the second class as the modifier class.
Paste this snippet in your variations.css
file:
/* css-variables-pro/button-variations/variations.css */ * { border: 0; } :root { --primary: #0076c6; --secondary: #333333; --error: #ce0606; --success: #009070; --white: #ffffff; } /* base style for all buttons */ .btn { padding: 1rem 1.5rem; background: transparent; font-weight: 700; border-radius: 0.5rem; cursor: pointer; outline: none; } /* variations */ .primary { background: var(--primary); color: var(--white); } .secondary { background: var(--secondary); color: var(--white); } .success { background: var(--success); color: var(--white); } .error { background: var(--error); color: var(--white); } .link { color: var(--primary); }
The btn
class contains the base styles for all the buttons and the variations come in where the individual modifier classes get access to their colors, which are defined at the :root
level of the document. This is extremely helpful not just for buttons, but for other elements in your HTML that can inherit the custom properties.
For example, if tomorrow you decide the value for the --error
custom property is too dull for a red color, you can easily switch it up to #f00000
, and voila: all elements using this custom property are updated with a single change. I don’t know about you, but that sounds like a stress-reliever to me.
Here’s what your first project should look like:
In the second project, we’ll build a light-and-dark theme. The light theme will take effect by default unless the user already has their system set to a dark theme. On the page, we’ll create a toggle button that allows the user to switch between themes.
Open the css-variables-pro
folder you created earlier. Inside the folder, create another folder for your second project and name it theming
. Or, you can use this command:
# create a new folder called theming mkdir theming
Next, move into the theming folder.
cd theming
Create three new files.
# create three files namely theme.html, theme.css, and theme.js touch theme.html theme.css theme.js
This part involves a bit of JavaScript. First, open your theme.html
and paste the following markup.
<!-- theme.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>CSS Variables - Theming</title> <link rel="stylesheet" href="theme.css" /> </head> <body> <header> <div class="container"> <div class="container-inner"> <a href="#" class="logo">My Blog</a> <div class="toggle-button-container"> <label class="toggle-button-label" for="checkbox"> <input type="checkbox" class="toggle-button" id="checkbox" /> <div class="toggle-rounded"></div> </label> </div> </div> </div> </header> <article> <div class="container"> <h1 class="title">Title of article</h1> <div class="info"> <div class="tags"> <span>#html</span> <span>#css</span> <span>#js</span> </div> <span>1st March, 2020</span> </div> <div class="content"> <p> Lorem ipsum dolor sit amet consectetur adipisicing elit. <a href="#">Link to another url</a> Eius, saepe optio! Quas repellendus consequuntur fuga at. Consequatur sit deleniti, ullam qui facere iure, earum corrupti vitae laboriosam iusto eius magni, adipisci culpa recusandae quis tenetur accusantium eum quae harum autem inventore architecto perspiciatis maiores? Culpa, officiis totam! Rerum alias corporis cupiditate praesentium magni illo, optio nobis fugit. </p> <p> Eveniet veniam ipsa similique atque placeat dignissimos quos reiciendis. Odit, eveniet provident fugiat voluptatibus esse culpa ullam beatae hic maxime suscipit, eum reprehenderit ipsam. Illo facilis doloremque ducimus reprehenderit consequuntur cupiditate atque harum quaerat autem amet, et rerum sequi eum cumque maiores dolores. </p> </div> </div> </article> <script src="theme.js"></script> </body> </html>
This snippet represents a simple blog page with a header, a theme toggle button, a dummy article, and links to both the corresponding CSS and JavaScript files.
Now, open the theme.css
file and paste the following.
:root { --primary-color: #0d0b52; --secondary-color: #3458b9; --font-color: #424242; --bg-color: #ffffff; --heading-color: #292922; --white-color: #ffffff; } /* Layout */ * { padding: 0; border: 0; margin: 0; box-sizing: border-box; } html { font-size: 14px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } body { background: var(--bg-color); color: var(--font-color); } .container { width: 100%; max-width: 768px; margin: auto; padding: 0 1rem; } .container-inner { display: flex; justify-content: space-between; align-items: center; } /* Using custom properties */ a { text-decoration: none; color: var(--primary-color); } p { font-size: 1.2rem; margin: 1rem 0; line-height: 1.5; } header { padding: 1rem 0; border-bottom: 0.5px solid var(--primary-color); } .logo { color: var(--font-color); font-size: 2rem; font-weight: 800; } .toggle-button-container { display: flex; align-items: center; } .toggle-button-container em { margin-left: 10px; font-size: 1rem; } .toggle-button-label { display: inline-block; height: 34px; position: relative; width: 60px; } .toggle-button-label .toggle-button { display: none; } .toggle-rounded { background-color: #ccc; bottom: 0; cursor: pointer; left: 0; position: absolute; right: 0; top: 0; transition: 0.4s; } .toggle-rounded:before { background-color: #fff; bottom: 4px; content: ''; height: 26px; left: 4px; position: absolute; transition: 0.4s; width: 26px; } input:checked + .toggle-rounded { background-color: #9cafeb; } input:checked + .toggle-rounded:before { transform: translateX(26px); } article { margin-top: 2rem; } .title { font-size: 3rem; color: var(--font-color); } .info { display: flex; align-items: center; margin: 1rem 0; } .tags { margin-right: 1rem; } .tags span { background: var(--primary-color); color: var(--white-color); padding: 0.2rem 0.5rem; border-radius: 0.2rem; }
This snippet can be divided into two main sections: the layout section and the custom properties section. The latter is what you should focus on. As you can see, the variables are applied above in the link, paragraph, heading, and article elements.
The idea behind this approach is that, by default, the website uses a light theme, and when the box is checked, the values for the light theme get inverted to a dark variant.
Since you can’t trigger these sitewide changes via CSS, JavaScript is critical here. In the next section, we’ll hook up the JavaScript code necessary to toggle between the light and dark themes.
Alternatively, you could trigger a change automatically via CSS using the prefers-color-scheme
media query to detect whether the user requested a light or dark theme. In other words, you can directly update the website to use the dark variants of the light theme.
Add the following snippet to all the CSS code you just wrote.
/* theme.css */ @media (prefers-color-scheme: dark) { :root { --primary-color: #325b97; --secondary-color: #9cafeb; --font-color: #e1e1ff; --bg-color: #000013; --heading-color: #818cab; } }
We’re are listening to the user’s device settings and adjusting the theme to dark if they’re already using a dark theme.
Update the theme.js
file by adding this snippet:
// theme.js const toggleButton = document.querySelector('.toggle-button'); toggleButton.addEventListener('change', toggleTheme, false); const theme = { dark: { '--primary-color': '#325b97', '--secondary-color': '#9cafeb', '--font-color': '#e1e1ff', '--bg-color': '#000013', '--heading-color': '#818cab' }, light: { '--primary-color': '#0d0b52', '--secondary-color': '#3458b9', '--font-color': '#424242', '--bg-color': '#ffffff', '--heading-color': '#292922' } }; function toggleTheme(e) { if (e.target.checked) { useTheme('dark'); localStorage.setItem('theme', 'dark'); } else { useTheme('light'); localStorage.setItem('theme', 'light'); } } function useTheme(themeChoice) { document.documentElement.style.setProperty( '--primary-color', theme\[themeChoice\]['--primary-color'] ); document.documentElement.style.setProperty( '--secondary-color', theme\[themeChoice\]['--secondary-color'] ); document.documentElement.style.setProperty( '--font-color', theme\[themeChoice\]['--font-color'] ); document.documentElement.style.setProperty( '--bg-color', theme\[themeChoice\]['--bg-color'] ); document.documentElement.style.setProperty( '--heading-color', theme\[themeChoice\]['--heading-color'] ); } const preferredTheme = localStorage.getItem('theme'); if (preferredTheme === 'dark') { useTheme('dark'); toggleButton.checked = true; } else { useTheme('light'); toggleButton.checked = false; }
Now let’s break down the current state of the website.
A user visits the page. The media query prefers-color-scheme
determines whether the user is using a light or dark theme. If it’s a dark theme, the website updates to use the dark variants of the custom properties. Let’s say a user isn’t using a dark theme or their OS doesn’t support a dark theme. The browser would default to the light theme, allowing the user to control that behavior by checking or unchecking the box.
Depending on whether the box is checked or unchecked, the useTheme()
function is called to pass in the theme variant and save the user’s current selection to local storage. You’ll see why it’s saved in a minute.
The useTheme()
function is where all the magic happens. Based on the theme variant passed, a lookup is performed on the theme
constant and used to switch between light and dark modes.
The last piece of the puzzle is persisting the current theme, which is achieved by reading the last preferred theme from local storage and setting it automatically when the user revisits the website.
You may be thinking of a million other ways to achieve this. Feel free to go through the code and make as many changes as you see fit.
Here’s what your second project should look like:
By building these simple projects, you can learn how to use CSS variables like a pro. There’s certainly more to them than I explained, so feel free to mess around with the code to explore further.
CSS variables help simplify the way you build websites and complex animations while still allowing you to write reusable and elegant code. To learn more, refer to the documentation on MDN.
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.
Vite is a versatile, fast, lightweight build tool with an exceptional DX. Let’s explore when and why you should adopt Vite in your projects.
Explore advanced capabilities for content sharing with the navigator.share
API, including dynamic content sharing, custom share targets, and batch sharing.
We spoke with Chas to get his insights on building technology and internal processes for companies that are scaling quickly.
Cypress is one of today’s foremost tools for testing web applications. Let’s explore when and why you should adopt Cypress in your projects.
2 Replies to "How to use CSS variables like a pro"
Please do not encourage people to use “outline: none” on a button element.
I strangely couldn’t retrieve the value from documentElement via getPropertyValue, as it was not set on that element. Setting the value via javascript to black works fine, and then retrieval works and comes back as black. (I had one variable definition in css under :root in header style definitions – red, and one value set on the html element itself via chrome devtools – black)
Instead using getComputedStyle worked okay as it supports the pseudo selector as second argument and I was able to retrieve the original css value of red, or any overloaded value as appropriate.
window.getComputedStyle(window.document.documentElement,”:root”).getPropertyValue(‘–joy-colour’)