A Strategy Guide To CSS Custom Properties

A Strategy Guide To CSS Custom Properties

A Strategy Guide To CSS Custom Properties

Michael Riethmuller

2018-05-14T13:30:38+02:00
2018-05-14T13:47:25+00:00

CSS Custom Properties (sometimes known as ‘CSS variables’) are now supported in all modern browsers, and people are starting to use them in production. This is great, but they’re different from variables in preprocessors, and I’ve already seen many examples of people using them without considering what advantages they offer.

Custom properties have a huge potential to change how we write and structure CSS and to a lesser extent, how we use JavaScript to interact with UI components. I’m not going to focus on the syntax and how they work (for that I recommend you read “It’s Time To Start Using Custom Properties”). Instead, I want to take a deeper look at strategies for getting the most out of CSS Custom Properties.

How Are They Similar To Variables In Preprocessors?

Custom Properties are a little bit like variables in preprocessors but have very some important differences. The first and most obvious difference is the syntax.

With SCSS we use a dollar symbol to denote a variable:

$smashing-red: #d33a2c;

In Less we use an @ symbol:

@smashing-red: #d33a2c;

Custom properties follow a similar conventions and use a -- prefix:

:root { --smashing-red: #d33a2c; }
.smashing-text { 
  color: var(--smashing-red);
}

One important difference between custom properties and variables in preprocessors is that custom properties have a different syntax for assigning a value and retrieving that value. When retrieving the value of a custom property we use the var() function.

The next most obvious difference is in the name. They are called ‘custom properties’ because they really are CSS properties. In preprocessors, you can declare and use variables almost anywhere, including outside declaration blocks, in media rules, or even as part of a selector.

$breakpoint: 800px;
$smashing-red: #d33a2c;
$smashing-things: ".smashing-text, .cats";

@media screen and (min-width: $breakpoint) {
  #{$smashing-things} {
    color: $smashing-red;
  }
}

Most of the examples above would be invalid using custom properties.

Custom properties have the same rules about where they can be used as normal CSS properties. It’s far better to think of them as dynamic properties than variables. That means they can only be used inside a declaration block, or in other words, custom properties are tied to a selector. This can be the :root selector, or any other valid selector.

:root { --smashing-red: #d33a2c; }

@media screen and (min-width: 800px) {
  .smashing-text, .cats {
    --margin-left:  1em;
  }
}

You can retrieve the value of a custom property anywhere you would otherwise use a value in a property declaration. This means they can be used as a single value, as part of a shorthand statement or even inside calc() equations.

.smashing-text, .cats {
  color: var(--smashing-red);
  margin: 0 var(--margin-horizontal);
  padding: calc(var(--margin-horizontal) / 2)
}

However, they cannot be used in media rules, or selectors including :nth-child().

There is probably a lot more you want to know about the syntax and how custom properties work, such as how to use fallback values and can you assign variables to other variables (yes), but this basic introduction should be enough to understand the rest of the concepts in this article. For more information on the specifics of how custom properties work, you can read “It’s Time To Start Using Custom Properties” written by Serg Hospodarets.

Dynamic vs. Static

Cosmetic differences aside, the most significant difference between variables in preprocessors and custom properties is how they are scoped. We can refer to variables as either statically or dynamically scoped. Variables in preprocessors are static whereas custom properties are dynamic.

Where CSS is concerned static means that you can update the value of a variable at different points in the compilation process, but this cannot change the value of the code that came before it.

$background: blue;
.blue {
  background: $background;
}
$background: red;
.red {
  background: $background;
}

results in:

.blue {
  background: blue;
}
.red {
  background: red;
}

Once this is rendered to CSS, the variables are gone. This means that we could potentially read an .scss file and determine it’s output without knowing anything about the HTML, browser or other inputs. This is not the case with custom properties.

Preprocessors do have a kind of “block scope” where variables can be temporarily changed inside a selector, function or mixin. This changes the value of a variable inside the block, but it’s still static. This is tied to the block, not the selector. In the example below, the variable $background is changed inside the .example block. It changes back to the initial value outside the block, even if we use the same selector.

$background: red;
.example {
  $background: blue;
  background: $background;
}

.example {
  background: $background;
}

This will result in:

.example {
  background: blue;
}
.example {
  background: red;
}

Custom properties work differently. Where custom properties are concerned, dynamically scoped means they are subject to inheritance and the cascade. The property is tied to a selector and if the value changes, this affects all matching DOM elements just like any other CSS property.

This is great because you can change the value of a custom property inside a media query, with a pseudo selector such as hover, or even with JavaScript.

a {
  --link-color: black;
}
a:hover,
a:focus {
  --link-color: tomato;
}
@media screen and (min-width: 600px) {
  a {
    --link-color: blue;
  }
}

a {
  color: var(--link-color);
}

We don’t have to change where the custom property is used — we change the value of the custom property with CSS. This means using the same custom property, we can have different values in different places or context on the same page.

Global vs. Local

In addition to being static or dynamic, variables can also be either global or local. If you write JavaScript, you will be familiar with this. Variables can either be applied to everything inside an application, or their scope can be limited to specific functions or blocks of code.

CSS is similar. We have some things that are applied globally and some things that are more local. Brand colors, vertical spacing, and typography are all examples of things you might want to be applied globally and consistently across your website or application. We also have local things. For example, a button component might have a small and large variant. You wouldn’t want the sizes from these buttons to be applied to all input elements or even every element on the page.


Source: Smashing Magazine