CSS Custom Properties and Theming

Avatar of Chris Coyier
Chris Coyier on (Updated on )

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

We posted not long ago about the difference between native CSS variables (custom properties) and preprocessor variables. There are a few esoteric things preprocessor variables can do that native variables cannot, but for the most part, native variables can do the same things. But, they are more powerful because of how they are live-interpolated. Should their values ever change (e.g. JavaScript, media query hits, etc) the change triggers immediate change on the site.

Cool, right? But still, how actually useful is that? What are the major use cases? I think we’re still seeing those shake out.

One use case, it occurred to me, would be theming of a site (think: custom colors for elements around a site). Rather than writing different CSS for a bunch of different themes, or writing JavaScript that targets all the elements we intend to change and changing them), we just write one base set of CSS that utilizes variables and set those variables to the theme colors.

Imagine we allow the header and footer background of our site to be customized.

header {
  background: var(--mainColor);
}

...

footer {
  background: var(--mainColor);
}

Maybe there is a subheader with a darker variation of that color. Here’s a little trick to lay a transparent layer of color over another:

.subheader {
  background: 
    /* Make a bit darker */
    linear-gradient(
      to top,
      rgba(0, 0, 0, 0.25),
      rgba(0, 0, 0, 0.25)
    )
    var(--mainColor);
}

Where does --mainColor come from?

With theming, the idea is that you ask the user for it. Fortunately we have color inputs:

<input type="color">

And you could store that value in a database or any other storage mechanism you want. Here’s a little demo where the value is stored in localStorage:

The value is plucked out of localStorage and used when the page loads. A default value is also set (in CSS), in case that doesn’t exist.

What makes the above demo so compelling, to me, is how little code it is. Maintaining this as a a feature on a site is largely a CSS endeavour and seems flexible enough to stand the test of time (probably).

Not unusually, I was way behind on this one.

Lots of people think of theming as one of the major use-cases for CSS Custom Properties. Let’s look at some other folks examples.

Giacomo Zinetti has the same kind of color-picker implementation

On his site:

Examples and advice from Harry Roberts

He wrote “Pragmatic, Practical, and Progressive Theming with Custom Properties”, in which he pointed to apps like Twitter and Trello that offer theming directly to users:

Harry does a lot of consulting, and to my surprise, finds himself working with companies that want to do this a lot. He warns:

Theming, the vast majority of the time, is a complete nice-to-have. It is not business critical or usually even important. If you are asked to provide such theming, do not do so at the expense of performance or code quality.

In Sass / In React

In a real-world application of theming through Custom Properties, Dan Bahrami recounts how they did it on Geckoboard, the product he works on:

It’s a React product, but they aren’t using any styles-in-JavaScript stuff, so they opted to do the theming with Custom Properties, through Sass.

@mixin variable($property, $variable, $fallback) {
  #{$property}: $fallback;
  #{$property}: var($variable);
}

So they can do:

.dashboard {
  @include variable(background, --theme-primary-color, blue);
}

Which compiles to having a fallback:

.dashboard {
  background: blue;
  background: var(--theme-primary-color);
}

They also created react-custom-properties which all about applying Custom Properties to components, taking advantage of the fact that you can set Custom Properties as inline styles:

<div style="--theme-primary-color: blue;">

</div>

More than one color and property

It’s not only colors that can change, a Custom Property can be any valid value. Here’s Keith Clark with a demo with multiple colors as well as font size:

And David Darnes with theming built into a Jekyll site:

https://twitter.com/DavidDarnes/status/857537860748136448

Microsoft Demo

Greg Whitworth created this demo (offline now, sorry):

Which uses color modifiers within color functions themselves:

.distant-building__window {
  fill: rgb(
    calc(111 + (111 * var(--building-r-mod))),
    calc(79 + (79 * var(--building-g-mod))),
    calc(85 + (85 * var(--building-b-mod)))
  );
}

Which is not supported in all browsers.

Greg also pointed out that CSS4 color functions (which we’ve covered before), will make all this theming stuff even more powerful.

The Polymer Project themes through Custom Properties

At least it did in the v1 docs. The idea is that you’d have a web compontent, like:

<iron-icon icon="[[toggleIcon]]">
</iron-icon>

That had smart defaults, but was specifically built to allow styling via theming:

<style>
  iron-icon {
    fill: var(--icon-toggle-color, rgba(0,0,0,0));
    stroke: var(--icon-toggle-outline-color, currentcolor);
  }
  :host([pressed]) iron-icon {
    fill: var(--icon-toggle-pressed-color, currentcolor);
  }
</style>

Which meant that you could set those variables and have the component take on new colors.

Support and fallbacks

Support has gotten pretty good recently:

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
4931No1610

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
12212312210.0-10.2

Opera Mini and IE are notably missing. We already covered the idea of a fallback through setting a valid non-variable property before the one using a Custom Property.

Like many modern CSS features, you can use @supports to test for support before using:

@supports (--color: red) {
  :root {
    --color: red;
  }
  body {
    color: var(--color);
  }
}

It always depends on the situation, but just putting fallbacks on a previous declaration is probably the most useful way to deal with non-support in CSS. There are also edge cases.

Michael Scharnagl documents a JavaScript method for testing:

if (window.CSS && window.CSS.supports && window.CSS.supports('--a', 0)) {
  // CSS custom properties supported.
} else {
  // CSS custom properties not supported
}

Colors and Accessibility

When setting colors for text, and the color behind that text, the contrast between those colors is an accessibility issue. Too little contrast, too hard to read.

One somewhat common solution to this is to choose whether the text should be light or dark (white or black) based on the color behind it.

David Halford has a demo calculating this with JavaScript:

And Brendan Saunders with Sass: