One feature that was on CSS wish lists long before it became a standard is CSS Variables, officially referred to as CSS Custom Properties in the specification. CSS Variables have been a standard now for almost ten years and all modern browsers have supported them for some time.
All developers should make use of this feature as it can save a lot of coding and maintenance time. In this tutorial, I’ll cover the basics of CSS Variables syntax and then I’ll go through some of the finer points of using CSS Variables (or custom properties) along with some practical use cases.
📚 Table of contents:
- CSS Variables in preprocessors #
- Variables (Custom Properties) in native CSS #
- Why “Custom Properties”? #
- Where are CSS Variables defined? #
- Technical notes about CSS Variables #
- Understanding the
var()
function # - Using CSS Variables with
calc()
# - Tricks with CSS Variables #
CSS Variables in preprocessors
CSS preprocessors have been using variables for well over 10 years. I won’t go into great detail on those options here, but I think it’s helpful to know how each of the popular preprocessor libraries use variables.
In Sass (or SCSS), you declare variables as follows:
$theme-color: #cccccc;
.element1 {
border: 1px solid $theme-color;
}
.element2 {
background-color: $theme-color;
}
Code language: PHP (php)
Notice the dollar sign ($). The first line is the declaration of the variable and its value. The two blocks that follow are examples of the variables in use later in the stylesheet.
Variables in Less.js use the @
symbol:
@linkcolor: firebrick;
a,.link {
color: @linkcolor;
}
And in Stylus variables look like this:
font-default = 14px
body
font-size font-default
Code language: JavaScript (javascript)
You can consult the individual documentation sections for each of the preprocessors shown above if you want to look further into variables using these third-party technologies.
Variables (Custom Properties) in native CSS
This brings us to CSS Variables, or Custom Properties, as they’re defined in the CSS specification. To declare a variable in plain CSS, place two hyphens in front of the custom name you choose for the variable, or property, then use the var()
function to place that value wherever you desire:
:root {
--main: #030303;
--accent: #5a5a5a;
}
.container {
color: var(--main);
}
.footer {
border-color: var(--main);
}
.sidebar {
color: var(--accent);
}
.module {
border-color: var(--accent);
}
Code language: CSS (css)
In this example, I’ve defined two CSS variables: --main
and --accent
. I’ve then used each CSS variable on two different elements, demonstrating the flexibility they open up.
As with variables in any programming language, this allows you to declare a variable in a single place. If you later want to change that value everywhere in your stylesheet, it’s simply a matter of changing the original declaration and it will apply to all the places where you use the variable.
Why “Custom Properties”?
In everyday conversation, developers often refer to this feature as “CSS variables”, in line with how preprocessors and programming languages refer to the same feature. But from a strictly technical perspective, these are not really “variables” but rather are Custom Properties.
They have the same syntax as any predefined property in CSS, except for the two dashes that appear in front of the property name. The two dashes allow for CSS authors to use any valid dashed identifier without fear of conflict with regular CSS properties.
The spec explains that two dashes alone are invalid (apparently reserved for future use) and CSS will never give meaning to any valid dashed-identifier beyond its use as an author-defined custom property.
Unlike regular CSS properties, Custom Properties are case sensitive. This means --main-color
is not the same as --Main-Color
. Valid characters to include in the custom property name are letters, numbers, underscores, and hyphens.
Where are CSS Variables defined?
As you can see in the example I provided above, you’ll often see CSS custom properties defined directly on the root element of an HTML document or shadow DOM. The :root
pseudo-class selector accomplishes this.
:root {
--main: #030303;
--accent: #5a5a5a;
}
Code language: CSS (css)
But CSS Variables aren’t limited to only being defined on the root element and it’s often beneficial to define them elsewhere. The :root
selector is commonly chosen because this always targets the uppermost element in the DOM tree (whether it’s the full document or shadow DOM).
In most cases, you’d get the same result by defining custom properties on the html
element (which is the root element in HTML documents) or even the body
element. Using :root
allows code to be more future-proof (e.g. if the spec one day add a new element as the root, the CSS would stay the same) and I suppose also allows for a stylesheet to apply to a different type of document that has a different root element.
For example, the following code would limit your custom properties for use only in the .sidebar
element:
.sidebar {
--main: #030303;
--accent: #5a5a5a;
}
Code language: CSS (css)
For demonstration purposes, you’ll often see variables defined on :root
, but you can use them anywhere it’s practical. In fact, many developers recommend setting them lower in the DOM tree for smaller modules during initial development, then working your way up towards :root
as you create values that are larger in scope.
Technical notes about CSS Variables
In addition to being able to apply to any element, CSS Variables are fairly flexible and easy to deal with.
Here are some things worth noting:
- They’re resolved with regular CSS inheritance and cascade rules
- You can use them in media queries and other conditional rules
- You can define them in an element’s
style
attribute - They can be read or set using features of the CSS Object Model.
It’s also notable that you can essentially nest CSS variables. Note the following example:
:root {
--main-color: lightblue;
--new-color: var(--main-color);
}
body {
background: var(--new-color);
}
Code language: CSS (css)
Notice I’ve defined a --main-color
variable then I’m using that same variable name as a value for the following CSS variable.
You can also use the !important
keyword in a CSS variable value, but this only applies the “importance” to the variable itself in relation to other variable definitions and not to the value that’s used on one or more elements in the document. If this is confusing, here’s an example:
:root {
--main-color: lightblue !important;
--main-color: orange;
}
body {
background: var(--main-color);
}
Code language: CSS (css)
In the above example, the background color would be “lightblue”, even though orange appears later in the variable definitions. But the background value itself on the body
element would not have any importance.
CSS Variables can also contain CSS-wide keyword values like initial
, inherit
, and unset
but the all
property does not affect CSS Variables (i.e. they’re not reset).
Understanding the var()
function
You’ve already seen the var()
function used in some typical examples in this CSS Variables tutorial. But there’s more to var()
than what I’ve covered so far.
First, the var()
function is valid only in a value; a property name or selector cannot use a CSS variable. Also, a media query value cannot use a CSS Variable (e.g. @media (max-width: var(--my-var))
is invalid).
The var()
function takes two arguments:
- The name of the custom property to include
- A fallback value in case the custom property is invalid
Here’s an example where the second argument takes effect:
:root {
--main-colour: lightgreen;
}
body {
background: var(--main-color, #ccc);
}
Code language: CSS (css)
Notice in the above code I’ve spelled the initial variable name using the British or Canadian word “colour” but when I used the variable I incorporated the American spelling “color”. This makes the variable technically invalid and so the plain grey (#ccc
) background takes effect instead.
Note also that a fallback value can contain its own commas. So for example, if your fallback is a font stack, this would be a valid way to define it:
:root {
--main-font: Helvetica, Arial, sans-serif;
}
body {
font-family: var(--main-type, "Courier New", Consolas, monospace);
}
Code language: CSS (css)
Notice again my variable has a flaw, declaring an unknown --main-type
instead of --main-font
. This triggers the fallback value, which is an alternate font stack. Thus, everything after the first comma (even including any other commas) is the fallback value.
Using CSS Variables for partials
When defining a CSS variable, the value it holds doesn’t have to be a valid CSS value in itself; it can be a partial value that can be used as part of a complete value.
For example, you can break up a font stack:
:root {
--main-font: "Franklin Gothic";
--fallback-font: Gill Sans;
--generic-font: sans-serif;
}
body {
font-family: var(--main-font), var(--fallback-font), var(--generic-font);
}
Code language: CSS (css)
In this case, each of the variable values works on its own, but it demonstrates the point. Let’s try a more contrived example using the rgba()
color notation:
:root {
--rgba-red: 25;
--rgba-green: 50;
--rgba-blue: 105;
--rgba-opacity: 0.4;
}
body {
background: rgba(var(--rgba-red), var(--rgba-green), var(--rgba-blue), var(--rgba-opacity));
}
Code language: CSS (css)
You can see how useful this might be, allowing you to essentially “build” values dynamically.
Using CSS Variables with calc()
One of the useful ways to incorporate CSS Variables into your projects is in conjuction with the calc()
function. As you might know, calc()
allows you to perform calculations within a value. So you can do something like this:
.element {
width: calc(100% - 100px);
}
Code language: CSS (css)
CSS custom properties allow you to take calc()
to the next level, for example with predefined sizes. Ahmad Shadeed described something similar and here’s an example:
:root {
--size: 240;
}
.module {
width: calc(var(--size) * 1px);
}
.module-small {
--size: 360;
}
.module-medium {
--size: 720;
}
.module-large {
--size: 1080;
}
Code language: CSS (css)
With this in place, I can use the .module
and .module-*
rule sets inside media queries, allowing me to show those styles conditionally for specific viewport sizes or other media features. The small/medium/large modules would have the same classes as the primary module but only the module size is overwritten as needed. In the above example, the initial 240
value for the size acts as sort of a default but I could also pass 240px
as a second argument in var()
to work as a fallback.
Tricks with CSS Variables
Several developers have shared tips and tricks using CSS Variables over the years. I won’t expand on those in detail here since this is mainly a beginners tutorial, but here’s a quick rundown of some:
- As I mentioned earlier, you can use CSS Variables in inline styles, as in the case of aspect ratio boxes.
- A space character is a valid value for a CSS variable, which allows you to do an on/off trick (e.g. for something like dark mode), which you can read about in Lea’s post.
- You can’t write hover styles in inline styles, but you can get around this using CSS Variables.
- CSS Variables help to more easily create multi-colored SVGs, as outlined here.
- You can build practical and maintainable design systems and themes with CSS Variables, expounded on in detail in this post
- You can use CSS Variables to build more efficient and maintainable grids using Grid Layout features, as described by Michelle Barker.
Conclusion
CSS Variables, or CSS Custom Properties, are ready to use today with well over 90% of globally in-use browsers supporting this handy feature. I hope this discussion of the basics and syntax will encourage you to consider CSS Variables in your newest projects, if you haven’t done so already.
If your use of CSS Variables has been limited to some global theme colors, maybe this tutorial will inspire you to make more use of them and possibly come up with some tricks of your own.
In case you have any questions regarding this CSS Variables tutorial, feel free to submit them in the comments below.
…
Don’t forget to join our crash course on speeding up your WordPress site. Learn more below:
Layout and presentation by Chris Fitzgerald and Karol K.