The power of CSS custom properties

27. September, 2022 3 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 adaption until late 2017. What Less or Sass offers since the very beginning is finally also supported in native CSS.

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 leverage 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;
  }
}

If we did not attach the dark-mode class to the <body>, our global styles wouldn’t be overridden, and all other elements would inherit from it. Alternatively, we can also leverage media queries, neat, right?

All other operations, such as calc, multiplication or addition, are also supported. CSS properties behave in that regard similar to 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 get or change variables from within JavaScript natively.

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

// get variable
styles.getPropertyValue("--primary");

// set variable
styles.setProperty("--primary", "#f00");

// or fast access
getComputedStyle(element).getPropertyValue("--primary");

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 leverage CSS custom properties as our get-to-go for variable declaration enabling easy dark mode integration 🥳

‘Till next time!