Working with CSS Custom Properties

27. September, 2022 4 min read Develop

Say hi to CSS variables

CSS custom properties, sometimes called CSS variables, are properties you can define in your CSS to reuse variables across your stylesheets.

Initially introduced in 2012, it took roughly five years to reach wide browser adoption, around late 2017 once Edge had caught up. What Less and Sass have offered since the very beginning is finally supported in native CSS too.

How to use it

A custom CSS property always needs to be defined within a selector:

.box {
  --color: #000;
  --spacing: 10px;
  color: var(--color);
  margin-bottom: var(--spacing);
}

/* not allowed */
--font-family: "Helvetica Now", Arial, sans-serif;

In the example above, we define the properties in the box selector. Properties cascade (including pseudo-elements), so if you have an element inside the box, they will inherit the variables from their parent. Let’s have a look at this:

.box {
  --spacing: 10px;
}

.box__inner {
  padding: var(--spacing);
}

As long as box__inner is within the box in the HTML document, box__inner will have access to --spacing. What if that is not the case? Well, we can make use of default values if a variable is not defined:

.box {
  --spacing: 10px;
}

.box__inner {
  color: var(--color, #000);
  /* or use another var as backup */
  padding: var(--custom-spacing, var(--spacing, 10px));
}

We can use this to create global styles. What would be the uppermost element in a document? Right, it is html or the :root pseudo-element:

:root {
  --spacing: 10px;
  --color: #000;
  --font-family: "Helvetica Now", Arial, sans-serif;
}

We can further override variables down the cascading tree. We can use this mechanic to, for example, introduce a dark mode:

:root {
  --color: #000;
  --background: #fff;
}

.dark-mode {
  --color: #fff;
  --background: #000;
}

/* or change it with media queries */
@media screen and (prefers-color-scheme: dark) {
  :root {
    --color: #fff;
    --background: #000;
  }
}

Attach the dark-mode class to the <body>, and our global values are overridden from there down, so every element inside inherits the dark set instead. Alternatively, we can let the media query do it for us, neat, right?

All other operations, such as calc, multiplication or addition, are also supported. Custom properties behave in that regard similarly to normal CSS property definitions.

:root {
  --spacing: 20px;
  --spacing-sm: calc(var(--spacing) / 2);
  --spacing-xl: calc(var(--spacing) * 2);
}

Using JavaScript

There is another advantage compared to Less or Sass. You can read and change variables from within JavaScript natively, at runtime.

// get a reference to the element
const root = document.querySelector(":root");

// set a variable
root.style.setProperty("--primary", "#f00");

// read it back, but only if it was set inline
root.style.getPropertyValue("--primary");

// read the value the browser actually resolved,
// including the ones declared in a stylesheet
getComputedStyle(root).getPropertyValue("--primary");

That distinction bites people. element.style.getPropertyValue() only ever sees inline declarations, so a variable you defined in your stylesheet comes back as an empty string. Use getComputedStyle() when you want the value that is actually in effect.

With that knowledge, we can do some interesting manipulations:

.container {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: 50px; height: 50px;
  background: green;
}
const root = document.documentElement;

root.addEventListener("mousemove", e => {
  root.style.setProperty('--x', e.clientX + "px");
  root.style.setProperty('--y', e.clientY + "px");
});

Courtesy of CSS Tricks, have a look at the demo.

Sass and CSS variables

You can also combine CSS custom properties with Sass, but be aware of the syntax:

$link-color: #333;

:root {
  --link-color: #{$link-color};
  --link-color-hover: #{darken($link-color, 25%)};
}

.link {
  color: var(--link-color);

  &:hover {
    color: var(--link-color-hover);
  }
}

It is good to combine different technologies to get the best out of them. We just recently updated the Control Panel to use CSS custom properties as our go-to for variable declaration, which made the dark mode integration a lot less painful 🥳

‘Till next time!