How To Define And Use CSS Custom Properties

A few years ago I offered a quick look at CSS custom properties, which provide a way to define and use variables directly in CSS. At the time the only browser with support was the nightly build of Firefox. Given the lack of support and that we could already use preprocessors like Sass to write variables, I promptly forgot all about custom properties after writing the post.

However, in the last month or two I’ve seen a few new articles about custom properties, which has led me to discover a few things.

First browser support is much better now. It’s about 70% globally and the latest version of nearly all browsers offer support. Second the spec has changed from being a working draft to a candidate recommendation and there are some differences in how to define custom properties since my earlier post.

I thought it would be a good time to revisit the topic and I’ve written a short series to show you how to work with custom properties in general and then to show you more practical examples.

One note before diving in. Every so often throughout the series, I’ll probably refer to CSS custom properties as variables of some type. If I do, know it’s because it’s less typing, it adds a bit of variety to the words I’m using, and for the most part, you’ll use custom properties as native CSS variables.

Why We Need Native CSS Variables

Before there were any preprocessors a lot of developers were calling for variables to become part of the CSS language. It’s far easier to maintain a stylesheet if you can define variables and have them all in one location within a file. You make a change to the value of the variable and it updates across your entire stylesheet.

At the time the people responsible for what does and doesn’t become part of CSS were against the idea. They wanted to keep the language simple and that meant not turning it into a programming language.

But developers wouldn’t be denied and preprocessors like Less and Sass arose to provide some of the programming functionality CSS didn’t have. Those who wanted variables had them and talk about native CSS variables quieted down.

Eventually, the maintainers of CSS relented and they added custom properties (variables) to the language. Do we need them now that we’ve become accustomed to working with Sass or Less variables?

One obvious answer is that not everyone uses a preprocessor so the ability to use variables natively is important, but even if you do work with a preprocessor, there are times where custom properties will still be necessary because variables in preprocessors have some limitations.

  • They can’t interact with Javascript or 3rd party stylesheets. Preprocessed variables are compiled into ordinary CSS property/value pairs before they come in contact with either.
  • They aren’t aware of the DOM or CSSOM. Again the preprocessed variable is compiled before HTML sees it and at that point the variable is no longer a variable.
  • They can’t be changed dynamically. Once again this is because the preprocessed variable is replaced on compile. It means you can’t use variables inside @media queries and change their values conditionally.
  • They don’t cascade. You can’t set a variable inside a selector to set or override the current value.
  • They don’t inherit. Defining a preprocessed variable on a selector doesn’t mean descendants of that selector can also use the variable.

Preprocessors make a developer’s life easier, but they don’t change anything in the browser itself as your Sass or Less code gets compiled into CSS before it’s able to interact with anything else.

If you want variables to interact with Javascript or be able to change dynamically inside a media query, then you need them to be native to CSS. If you don’t need that interaction, you can probably stick with your preprocessor of choice.

How to Define CSS Custom Properties

The authors of CSS didn’t create variables specifically. They created custom properties, which according to the spec are “a family of custom author-defined properties known collectively as custom properties.” They allow you, as the author of a document, to assign values to a property that you name and then use those values in other properties throughout your stylesheet.

Custom properties are like any other CSS property in that they can be declared on any element and they use normal inheritance and cascade rules. They can also be conditional inside @media and other rules. They can be used inside a <style> element or inside a separate stylesheet. They can also be read and set using CSSOM and they can interact with Javascript.

There are two components to working with custom properties, defining them and using them. You define a custom property by starting its name with two dashes – (U+002D hyphen minus) and assigning it a value.

1
2
3
--foo: red;
--foo: &quot;some text&quot;;
--foo: 99;

CSS property names are case sensitive so –foo and –Foo are two different custom properties as are the following two custom properties.

1
2
--background-color: red;
--Background-Color: red;

Like a lot of people, I wasn’t initially crazy about the double dash syntax when I first saw it. Many of the writing apps I use will automatically convert two dashes in a row into an em dash. However, there are good reasons for this syntax.

If you used $variable (the dollar sign notation) for CSS and also happen to use Sass or another preprocessor, then $variable would be converted when you compiled your preprocessed code.

The double dash syntax is written to allow you to work with preprocessor variables in additions to native CSS custom properties. I suspect that could also be part of the reason for calling them custom properties instead of variables.

You can’t declare custom properties in empty space like I did above. This is CSS so you have to declare the custom property on a selector.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
div {
  --background-color: red;
}

.special {
  --background-color: red;
}

.special:hover {
  --background-color: red;
}

div .special {
  --background-color: red;
}

In the code above I define a custom property on an element, a class, a pseudo element, and a descendent selector. You can declare a custom property on any valid selector, which might make you wonder where’s the best place to declare a custom property.

There’s no automatic best answer to the question, but you’ll commonly see custom properties set on :root, which is a pseudo-class that represents an element at the root of the document. In HTML 4, that’s always the element.

1
2
3
:root {
  --background-color: red;
}

The reason for defining custom properties on :root has to do with the ability of custom properties to cascade through a stylesheet. Anything set on :root will apply to its descendant selectors, which is any and every selector in your CSS file. This effectively makes every custom property set on :root a global variable that you can use throughout your stylesheet.

In addition to any value you assign to them, custom properties can also accept some CSS-wide keywords common to all CSS properties.

  • initial—applies initial (default) value as defined in the spec (might be empty or nothing in some cases)
  • inherit—applies the value of the parent element
  • unset—applies the inherited value if the property is normally inherited and the initial value if not
  • revert—applies the default value as set in the user-agent’s style sheet (most likely empty in the case of CSS custom properties).

As for values you assign, they can be any valid CSS value so numbers, strings, lengths, colors, etc. are all valid. Values can even be expressions and the syntax for custom properties is quite permissive. For example the following code I grabbed from the spec is valid.

1
--foo: if(x < 5) this.width = 10;

It’s not exactly useful on its own. It’s invalid on normal properties, but something like this could become useful with a little Javascript interaction.

How to Use CSS Custom Properties

Defining a custom property is one half of the equation. Using them is the other half. To use a custom property you make use of the var() function.

1
var() = var( <custom-property-name> [, <declaration-value> ]? )

The var() function can be used in place of any part of a value in any CSS property.

1
2
3
4
5
6
7
:root {
  --accent-color: red;
}

h1 {
  color: var(--accent-color);
}

Here I defined the custom property –accent-color on the :root pseudo-class and then used the var() function to call the custom property as the value of the color of the h1 element.

The value of the custom property (red) will be substituted in place of var(–accent-color) when the value of color is computed, resulting in h1s being red. Because the custom property was defined on :root, it cascades down through the CSSOM and can be used on h1 elements.

On the other hand, this wouldn’t work.

1
2
3
4
5
6
7
h1 {
  --accent-color: red;
}

h2 {
  color: var(--accent-color);
}

Here the custom property has been defined on the h1 making it available to any h1 and its descendent selectors in the document, but nothing else.

Invalid Variables

A custom property declaration can be seen as invalid when its value is computed. For example:

1
2
3
4
5
6
7
8
9
10
11
:root {
 --not-a-color: 20px;
}

p {
 background-color: red;
}

p {
 background-color: var(--not-a-color);
}

Here the variable not-a-color wants to be used as a color, but 20px is clearly not a color. When the value 20px is substituted in background-color, the declaration will be invalid.

Something to note is that normally declaring background-color: 20px would be a syntax error and the declaration would be ignored. With custom properties the computed value will either be the initial or inherited value of the property, depending on the specific property in question.

In the previous example, paragraphs will have a transparent background because that’s the initial value for the background-color of a paragraph. You might think they’d be red, since red was defined directly above the invalid variable, but it’s not the case and it’s something to keep in mind.

One way you can’t use custom properties is as part of the regular property name or selector. Trying to do so will produce invalid syntax.

1
2
3
4
.foo {
  --side: margin-top;
  var(--side): 20px;
}

You might think the second declaration would become margin-top: 20px, but it throws syntax error for having an invalid property name of var(–side).

Fallback Values

You probably noticed a second argument to var() when I first showed the general syntax. Here’s the var() syntax again, if you didn’t.

1
var() = var( <custom-property-name> [, <declaration-value> ]? )

The second argument, <declaration-value>, allows you to provide a fallback value in case the reference to the custom property is invalid. It provides a way for you to set defaults in a component stylesheet that can then be overridden in your main stylesheet, which is something very useful for your own libraries or those you use from 3rd parties.

1
2
3
h1 {
  background-color: var(--header-color, blue);
}

In the example above I added blue as the declaration-value so if for any reason the reference to header-color is invalid, the background-color on the h1 will be blue. If the reference is valid then the value of the custom property will be used.

The use of fallback values also means you can define and use a custom variable in place instead of having to define them first and then use var() later. The example above works regardless of whether or not –header-color had been previously defined, though it’s scope would be h1s and descendent only.

You can use commas in the fallback, which is useful for properties like font-family where you can provide a comma separated list of values.

1
(--heading-font, Helevetica, Verdana, sans-serif);

Operations

If you work with variables in preprocessors you might have come to appreciate being able to perform operations on them.

1
2
$columns: 4
$width: 100% / $columns;

This won’t work with custom properties as CSS doesn’t have operators built into the language. Instead you have to use the calc() function.

1
2
--columns: 4
--width: calc( 100%/var(--columns) );

This is a bit more verbose, but it effectively does the same thing as the previous block of code. You’ll find the calc() function very useful when working with custom properties.

Closing Thoughts

Hopefully that wasn’t too difficult an introduction to CSS custom properties. They’re pretty easy to work with once you get used to the syntax. Even if you currently use variables in preprocessors, there are good reasons to consider creating variables directly in CSS.

The main reason being that custom properties can interact with the DOM or CSSOM and be changed dynamically. Their ability to follow cascading and inheritance rules is another.

I kept the examples here simple in order to make concepts easier to understand, but I would like to offer more practical examples and that’s what I’ll be doing over the next few weeks.

I’ll skip next Tuesday as it’s the 4th of July here in the States, but I’ll come back the week after with some examples for writing more readable code and in the weeks after I’ll show you how you might use custom variables inside @media queries, how you might change them dynamically with and without Javascript, and finally how you might work with custom properties in combination with animation.

Download a free sample from my book, Design Fundamentals.

2 comments

  1. Nice and easy to understand article on CSS Custom properties.

    Is there a typo error in “Hopefully that was too difficult an introduction to CSS custom properties.”. It should have “not too difficulty”.

    Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *