CSS Custom Properties are here.

Jenny Veens
Playground Inc.
Published in
8 min readSep 17, 2018
CSS Custom Properties allow for some new CSS experimentation.

In this article I’m going to tell you all about CSS Custom Properties, as well as give you some examples of how to use them and why they’re so awesome!

Variables in CSS are no new idea. We’ve been using preprocessors like Sass and Less for some time now, partly for the convenience that they afford us with variables.

Using variables in our CSS stylesheets has several benefits:

  1. It allows us to keep any reusable values in one place. If a value changes, we no longer need to execute a find-and-replace to make the update. And as long as we have been using the variables for the appropriate CSS property values, we don’t have to worry that there might be a value lurking somewhere that was missed.
  2. We can reduce the amount of typing we need to do to set our CSS property values. A great example here is saving your font stack as a variable. Now you don’t need to remember each specific font-family and their order in the stack — you can just set it to a variable and reuse that throughout your styles.
  3. Variables can make our code easier to read. Now instead of mentally trying to decode a hex code in our mind, we can easily tell that it represents the ‘Primary’ brand colour, or maybe an ‘accent’ colour. And if we’ve been strategic in setting up our variables, the relationships between them become more apparent. (ex. column width & # of columns).

While preprocessor variables are an improvement over vanilla CSS, they do have some limitations:

  1. Our preprocessor variables compile down to pure CSS. Once compiled, it’s almost impossible* for us to tell (except by our own knowledge of how the CSS was written) which values were which variables.
    * Source maps provide us with a way to link our compiled CSS to its uncompiled preprocessor version. Primarily used in development but they can be sometimes spotted in the wild.
  2. Our preprocessor variables have no knowledge or connection to the DOM. We have no way of scoping or inheriting a variable. This means that all values are global, and they will always have a static value.
  3. Related to the point above, media queries have always been a bit troublesome with preprocessor variables. Since we can’t scope them, there is no way to redefine them within a media query block, and typically we are stuck with just creating sm, md, lg variations of our variables and using them in the appropriate query. Additionally, we must redeclare our CSS rules in each breakpoint, leading to lots of repetitive code.

CSS custom properties bring us all the things that we’ve been loving about our old friends, preprocessor variables, as well as helping us overcome some of the limitations.

That’s great. How do I use them?

CSS Custom Properties are pure CSS, and they behave the same way other properties do in terms of the cascade and inheritance. Like other properties, custom properties must be defined on a CSS selector to be valid. To make a variable available globally, we can define it on the :root element.

Custom properties are defined with a -- prefix. Custom property names can be any combination of alphanumeric characters and or _. Unlike regular CSS properties, they are case-sensitive.

:root {
--my-new-custom-property: #BADA55;
}

To access the value of our variable, we don’t just use the property name as you might think:

/* DOESN'T WORK */.selector {
background-color: --my-new-custom-property;
}

CSS now has a var() function, which we can use to extract the value of our custom property:

/* WORKS! */.selector {
background-color: var(--my-new-custom-property);
}

The var() function also takes an optional second parameter, which will be a default value if the custom property isn’t defined.

.selector {
background-color: var(--my-new-custom-property, #ffff00);
}

It’s worth noting, that unlike with our preprocessor variables, our custom properties can only be used as values for existing CSS properties. We cannot use their values as property names, or CSS selectors.

Scoping Custom Properties

Scope allows us to define the value of a variable in a specific context. I mentioned earlier that CSS Custom Properties are aware of the DOM, so we can leverage the normal behaviour of the CSS cascade and inheritance to change the value of our custom properties.

This is very useful for theming components, or entire pages. In these cases, a custom property defined further down in the CSS, or with a more specific selector than the initial declaration will win out and be the value that the browser uses to render the element.

In this example, our .button styles are inverted when the button is hovered.

.button {
color: white;
background-color: midnightblue;
}
.button:hover {
color: midnightblue;
background-color: white;
}

We can clean this up with CSS Custom Properties. Let’s add in some variables and update the styles for our buttons:

:root {
--foreground: midnightblue;
--background: white;
}
.button {
color: var(--background);
background-color: var(--foreground);
}
.button:hover {
color: var(--foreground);
background-color: var(--background);
}

We’ve defined 2 variables to style our buttons, they are --foreground and --background. Now when we hover our buttons those values get inverted. We can utilize the CSS cascade to update those custom properties for themed sections.

.themed {
--foreground: peachpuff;
--background: darkslateblue;
}

Since the .themed selector is more specific than :root and is located further down in our document, the cascade and inheritance will overwrite these values for any child elements of .themed. Now any buttons within a themed element will get the new custom property values applied. There is no need to redefine the button styles with the new colours! The text and background colours of each section are also defined with these same variables, and there is no need to redefine the styles here either. Big wins!

For a full demo, have a look at this codepen:

An example of how we can scope variables to theme sections of our website.

Streamlined Media Queries

The current state of media queries with preprocessor variables goes something like this:

  1. Decide which breakpoints you will work with
  2. For each breakpoint, create a new variation of the variable that needs to change (eg. $gutter-lg, $gutter-md, $gutter-sm).
  3. Work with the appropriate variable in each media query.

For example, to change the columns and gutters for our grid, we might do something like this:

$columns-sm: 1;
$columns-md: 2;
$columns-lg: 3;
$gutters-sm: 10px;
$gutters-md: 20px;
$gutters-lg: 30px;
.grid-container {
display: grid;
grid-template-columns: repeat($columns-sm, 1fr);
grid-column-gap: $gutters-sm;
grid-row-gap: $gutters-sm;
}
@media (min-width: 600px) {
.grid-container {
grid-template-columns: repeat($columns-md, 1fr);
grid-column-gap: $gutters-md;
grid-row-gap: $gutters-md;
}
}
@media (min-width: 1000px) {
.grid-container {
grid-template-columns: repeat($columns-lg, 1fr);
grid-column-gap: $gutters-lg;
grid-row-gap: $gutters-lg;
}
}

This approach clearly has some problems:

  1. We have to define as many variables as we have breakpoints, just to cover the changes that need to happen within each media query, or we could define multiple values within a map — either way, it’s not as simple as it could be.
  2. Our variables are all globally scoped, meaning that we can accidentally use the wrong variable variation in any breakpoint.
  3. This is a somewhat simple example, and we can already see the repetition required to make our styles work.

With CSS Custom Properties, we can simply update our variables and let the CSS cascade take care of the rest!

:root {
--columns: 1;
--grid-gutters: 10px;
}
.grid-container {
display: grid;
grid-template-columns: repeat(var(--columns, 1), 1fr);
grid-column-gap: var(--grid-gutters, 15px);
grid-row-gap: var(--grid-gutters, 15px);
}
@media (min-width: 600px) {
:root {
--columns: 2;
--grid-gutters: 20px;
}
}
@media (min-width: 1000px) {
:root {
--columns: 3;
--grid-gutters: 30px;
}
}

Check out this example below to see what I mean:

CSS Custom Properties + Media Queries = ❤️

You can compare the above example with a similar version written with Sass variables instead of custom properties here:

An example of the ways that media queries with Sass are less than ideal.

Other Amazing things we can do:

Calc()
The CSS function calc() was just meant for CSS Custom Properties (okay, so not actually, but they work so well together!). We can let calc take care of all the math and completely change our layouts by updating our variables.

:root {
--columns: 2;
--gutters: 10px;
}
.calc-cards {
display: flex;
flex-wrap: wrap;
margin: calc((var(--gutters) / 2) * -1);
}
.calc-card {
width: calc((100% / var(--columns)) - var(--gutters));
height: 200px;
margin: calc(var(--gutters) / 2);
background-color: deeppink;
}

In the below example, try changing the values for --columns or --gutters and see what happens to the layout:

Update the variables in this Codepen and watch the layout change like magic!

Update with JavaScript:
Custom properties don’t get compiled down to their values. Anywhere that they are used with var() as a property value remains when we view the site in our browser.

CSS Variables live on when we view our site in the browser.

We can take advantage of this and update them dynamically with JavaScript!

Using JavaScript, we can update any of our custom variables on a selected element by calling .style.setProperty(customPropertyName, customPropertyValue).

Check out this CodePen, where we can easily update the CSS using HTML inputs and JS:

Update the styles here using the inputs

That’s great, but is this something we can actually use?

Current support for custom properties is looking really good!

Screenshot from caniuse.com taken August 16, 2018

If you need to support older browsers or IE, you can always set your styles for those browsers, then update your CSS to use custom properties within feature queries.

// set styles here that will be used in browsers that don't support
.selector {
background-color: red;
}
// Use a fake variable to test if the browser supports variables
@supports (--test-var: value) {
// Overwrite our previous rule to use custom properties
.selector {
background: var(--primary-color);
}
}

Yes, if you need to support older browsers, it does take a little more work. But the features custom properties bring us make it so worth it!

We’re actually already using CSS Custom Properties here at Playground Inc — A while back, we created a Playbook to contain our policies. Each section of the playbook is themed a different colour, so it seemed like the perfect place to make use of custom properties. View the site here: playbook.playgroundinc.com.

Thank Yous

HUGE THANKS to Lea Verou, Una Kravets, and Wes Bos for creating such incredible resources for learning about the latest and greatest in web development. I would still be struggling to work these things out myself if it wasn’t for you.

--

--

Jenny Veens
Playground Inc.

Senior Developer Playground Inc <> Educator Juno College (formerly HackerYou)