Variables in CSS: Custom Properties

The following is a short extract from Tiffany’s book, CSS Master, 2nd Edition.

For years, variables were one of the most commonly requested CSS features. Variables make it easier to manage colors, fonts, size, and animation values, and to ensure their consistency across a codebase.

It took years to work through the details of the syntax and decide how variables would fit into existing rules governing the cascade and inheritance. Now they’re available to developers in the form of CSS “custom properties”.

--ADVERTISEMENT--

In this chapter, we’ll discuss the syntax of CSS custom properties. We’ll look at:

  • how to define properties and set default values for those properties
  • how the rules of the cascade and inheritance work with custom properties
  • how to use custom properties with media queries

By the end, you should have a good grasp of how to use custom properties in your projects.

Note: Browser support for custom variables is robust, existing in the latest versions of every major browser. Support is not, however, available in older yet recently released browser versions that may still be widely used by your site’s audience. Versions of Microsoft Edge prior to 15 and Safari prior to version 9.1 lack support entirely. The same is true for any version of Internet Explorer. Microsoft Edge 15 has support, but also has a few documented bugs.

Defining a Custom Property

To define a custom property, select a name and prefix it with two hyphens. Any alphanumeric character can be part of the name. Hyphen (-) and underscore (_) characters are also allowed. A broad range of unicode characters can be part of a custom property name, including emojis. For the sake of clarity and readability, stick to alphanumeric names.

Here’s an example:

--primarycolor: #0ad0f9ff; /* Using #rrggbbaa color notation */

The -- indicates to the CSS parser that this is a custom property. The value of the property will replace the property wherever it’s used as a variable.

Custom property names are case-sensitive. In other words, --primaryColor and --primarycolor are considered two distinct property names. That’s a departure from traditional CSS, in which property and value case doesn’t matter. It is, however, consistent with the way ECMAScript treats variables.

As with other properties, such as display or font, CSS custom properties must be defined within a declaration block. One common pattern is to define custom properties within a ruleset that uses the :root psuedo-element as a selector:

:root {
    --primarycolor: #0ad0f9ff;
}

:root is a pseudo-element that refers to the root element of the document. For HTML documents, that’s the html element. For SVG documents, it’s the svg element. By using :root, properties are immediately available throughout the document.

Using Custom Properties

To use a custom property value as a variable, we need to use the var() function. For instance, if we wanted to use our --primarycolor custom property as a background color, we’d use the following:

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

Our custom property’s value will become the computed value of the background-color property.

To date, custom properties can only be used as variables to set values for standard CSS properties. You can’t, for example, store a property name as a variable and then reuse it. The following CSS won’t work:

:root {
    --top-border: border-top; /* Can't set a property as custom property's value */
    var(--top-border): 10px solid #bc84d8 /* Can't use a variable as a property */
}

You also can’t store a property–value pair as a variable and reuse it. The following example is also invalid:

:root {
   --text-color: 'color: orange'; /* Invalid property value */
}
body {
  var(--text-color); /* Invalid use of a property */
}

Lastly, you also can’t concatenate a variable as part of a value string:

:root {
    --base-font-size: 10;
}
body {
    font: var(--base-font-size)px / 1.25 sans-serif; /* Invalid CSS syntax. */
}

Custom properties were designed to be used as properties that are parsed according to the CSS specification. Should the CSS Extensions specification be adopted by browser vendors, we could someday use custom properties to create custom selector groups, or custom at-rules. For now, however, we’re limited to using them as variables to set standard property values.

Setting a Fallback Value

The var() function actually accepts up to two arguments. The first argument should be a custom property name. The second argument is optional, but should be a declaration value. This declaration value functions as a kind of fallback value if the custom property value hasn’t been defined.

Let’s take the following CSS:

.btn__call-to-action {
    background: var(--accent-color, salmon);
}

If --accent-color is defined—let’s say its value is #f30—then the fill color for any path with a .btn__call-to-action class attribute will have a red-orange fill. If it’s not defined, the fill will be salmon.

Declaration values can also be nested. In other words, you can use a variable as the fallback value for the var function:

body {
    background-color: var(--books-bg, var(--arts-bg));
}

In the CSS above, if --books-bg is defined, the background color will be set to the value of the --books-bg property. If not, the background color will instead be whatever value was assigned to --arts-bg. If neither of those are defined, the background color will be the initial value for the property—in this case transparent.

Something similar happens when a custom property is given a value that’s invalid for the property it’s used with. Consider the following CSS:

:root {
    --footer-link-hover: #0cg; /* Not a valid color value. */
}
a:link {
     color: blue;
}
a:hover {
    color: red;
}
footer a:hover {
    color: var(--footer-link-hover);
}

In this case, the value of the --footer-link-hover property is not a valid color. In Microsoft Edge, the hover state color for footer links will be inherited from the a:hover selector. In most other browsers, the hover state color will be inherited from the text color of the body element.

Custom Properties and the Cascade

Custom properties also adhere to the rules of the cascade. Their values can be overridden by subsequent rules:

:root {
    --text-color: #190736; /* navy */
}
body {
   --text-color: #333;  /* Dark gray */
}
body {
  color: var(--text-color);
}

In the example above, our body text would be dark gray. We can also reset values on a per-selector basis. Let’s add a couple more rules to this CSS:

:root {
    --text-color: #190736; /* navy */
}
body {
   --text-color: #333;  /* Dark gray */
}
p {
  --text-color: #f60; /* Orange */
}
body {
  color: var(--text-color);
}
p {
  color: var(--text-color)
}

In this case, any text that’s wrapped in p element tags would be orange. But text within div or other elements would still be dark gray.

It’s also possible to set the value of a custom property using the style attribute—for example, style="--brand-color: #9a09af"—which can be useful in a component-based, front-end architecture.