On Using Custom Properties

Written by / Original link on Oct. 22, 2021


To me, the single greatest addition to CSS over the past 20 years is Custom Properties. They’ve opened up a ton of possibilities, and make us CSS Authors way more productive. The way they are specced — as properties and not simply variables — is pure genius and gives them their true power.


💁‍♂️ I heard spec author Tab Atkins talk about them for the first time at CSS Day 2013, but it wasn’t until Lea Verou‘s talk CSS Variables: var(--subtitle); in 2016 that it really clicked for me.


Over at CSS-Tricks, Chris Coyier writes about the Different Degrees of Custom Property Usage: how do you provide defaults, and how far do you go when splitting things up into several parts?

Regarding the fallback part Chris sticks to what we already know: a 2nd argument to var():

el {
  border: .1em solid var(--color, hsl(200deg 15% 73%));

The interesting part is on splitting things up: do you provide one single hsl color, or do you split out all the individual parts?

el {
  --color: hsl(200deg 15% 73%);
  border: .1em solid var(--color);
el {
  --color-h: 200deg;
  --color-s: 15%;
  --color-l: 73%;
  --color-hsl: var(--color-h) var(--color-s) var(--color-l);
  --color: hsl(var(--color-hsl));
  border: .1em solid var(--color);

Just like Chris I’m more leaning towards using --color here because although these Custom Properties are very nice, they do come at a cost: complexity. As I’ve tweeted back in February:

Every time I encounter a CSS demo that's heavily relying on Custom Properties, I'm reminded of Gall's Law:

“A complex system that works is invariably found to have evolved from a simple system that worked.”

👉 It takes me extra effort to decipher how the code works.

— Bramus (@bramus) February 22, 2021

Thankfully there’s DevTools’s “Computed Styles” pane to somewhat help me out there 🙂

Additionally, settings these individual props on :root and then overriding --color-h for some specific element won’t work, because --color will already have been computed before it is passed down the inheritance tree. This is what Chris calls The Big Gotcha With Custom Properties.


Over at her blog, Lea Verou also wrote about Custom Properties and how to provide defaults. Interestingly, Lea opts to no directly use the passed in Custom Property, but resorts to something she calls “pseudo-private custom properties” that get an extra _ prefix.

My preferred solution is what I call pseudo-private custom properties. You use a different property internally than the one you expose, which is set to the one you expose plus the fallback.

Like so:

el {
  --_color: var(--color, hsl(200deg 15% 73%));
  border: .1em solid var(--_color);

As an extra she also taps into Houdini’s powerful @property to register a default value. Unfortunately Houdini is only supported in Chromium at the time of writing.

🎩 Houdini, ain't that a magician?

Houdini is a set of low-level APIs that exposes parts of the CSS engine, giving developers the power to extend CSS by hooking into the styling and layout process of a browser’s rendering engine. Houdini is a group of APIs that give developers direct access to the CSS Object Model (CSSOM), enabling developers to write code the browser can parse as CSS, thereby creating new CSS features without waiting for them to be implemented natively in browsers.

It really is magic, hence it's name Houdini. I'd recommend this slidedeck and this video to get you started


👀 Don’t try this at home

Because Custom Properties are not parsed until they are used, you can store anything in them. That means you could abuse them to create faux Single-line Comments in CSS:

😅 Single-line Comments in CSS using Custom Properties.

Advantage: The @ChromeDevTools expose 'm to those snooping around.

Disadvantage: Waste of CPU cycles and memory as these values get passed to the CSS engine to simply sit there doing nothing.

— Bramus (@bramus) March 1, 2021


Related: The Power of Composition with CSS Custom Properties →

bram bram link tutorialzine bram

« Natively Format JavaScript Dates and Times - Tonic — A thin wrapper around Web Components »