Custom Properties as State

Avatar of Chris Coyier
Chris Coyier on

Take the pain out of building site search with the Algolia hosted API. Start free now!

Here’s a fun idea from James Stanley: a CSS file (that presumably updates daily) containing CSS custom properties for “seasonal” colors (e.g. spring is greens, fall is oranges). You’d then use the values to theme your site, knowing that those colors change slightly from day to day.

This is what I got while writing this:

:root {
  --seasonal-bg: hsl(-68.70967741935485,9.419354838709678%,96%);
  --seasonal-bgdark: hsl(-68.70967741935485,9.419354838709678%,90%);
  --seasonal-fg: hsl(-68.70967741935485,9.419354838709678%,30%);
  --seasonal-hl: hsl(-83.70967741935485,30.000000000000004%,50%);
  --seasonal-hldark: hsl(-83.70967741935485,30.000000000000004%,35%);
}

I think it would be more fun if the CSS file provided was just the custom properties and not the opinionated other styles (like what sets the body background and such). That way you could implement the colors any way you choose without any side effects.

CSS as an API?

This makes me think that a CDN-hosted CSS file like this could have other useful stuff, like today’s date for usage in pseudo content, or other special time-sensitive stuff. Maybe the phase of the moon? Sports scores?! Soup of the day?!

/* <div class="soup">The soup of the day is: </div> */
.soup::after {
  content: var(--soupOfTheDay); /* lol kinda */
}

It’s almost like a data API that is tremendously easy to use. Pseudo content is even accessible content these days — but you can’t select the text of pseudo-elements, so don’t read this as an actual endorsement of using CSS as a content API.

Custom Property Flexibility

Will Boyd just blogged about what is possible to put in a custom property. They are tremendously flexible. Just about anything is a valid custom property value and then the usage tends to behave just how you think it will.

body {
  /* totally fine */
  --rgba: rgba(255, 0, 0, 0.1);
  background: var(--rgba);

  /* totally fine */
  --rgba: 255, 0, 0, 0.1;
  background: rgba(var(--rgba));

  /* totally fine */
  --rgb: 255 0 0;
  --a: 0.1;
  background: rgb(var(--rgb) / var(--a));
}

body::after {
  /* totally fine */
  --song: "I need quotes to be pseudo content \A and can't have line breaks without this weird hack \A but still fairly permissive (💧💧💧) ";
  content: var(--song);
  white-space: pre;
}

Bram Van Damme latched onto that flexiblity while covering Will’s article:

That’s why you can use CSS Custom Properties to:

perform conditional calculations

pass data from within your CSS to your JavaScript

inject skin tone / hair color modifiers onto Emoji 

toggle multiple values with one custom property (--foo: ; hack)

Bram points out this “basic” state-flipping quality that a custom property can pull off:

:root {
  --is-big: 0;
}

.is-big {
  --is-big: 1;
}

.block {
  padding: calc(
    25px * var(--is-big) +
    10px * (1 - var(--is-big))
  );
  border-width: calc(
    3px * var(--is-big) +
    1px * (1 - var(--is-big))
  );
}

Add a couple of scoops of complexity and you get The Raven (media queries with custom properties).

I’d absolutely love to see something happen in CSS to make this easier. Using CSS custom properties for generic state would be amazing. We could apply arbitrary styles when the UI is in arbitrary states! Think of how useful media queries are now, or that container queries will be, but compounded because it’s arbitrary state, not just state that those things expose.

Bram covered that as well, mentioning what Lea Verou called “higher level custom properties”:

/* Theoretical! */

.square {
  width: 2vw;
  padding: 0.25vw;
  aspect-ratio: 1/1;

  @if (var(--size) = big) {
    width: 16vw;
    padding: 1vw;
  }
}

.my-input {
  @if(var(--pill) = on) {
    border-radius: 999px;
   }
}

About that naming

Will calls them “CSS variables” which is super common and understandable. You’ll read (and I have written) sentences often that are like “CSS variables (a.k.a CSS Custom Properties)” or “CSS Custom Properties (a.k.a CSS Variables.” Šime Vidas recently noted there is a rather correct way to refer to these things: --this-part is the custom property and var(--this-part) is the variable, which comes right from usage in the spec.

JavaScript Library State… Automatically?

I’m reminded of this Vue proposal. I’m not sure if it went anywhere, but the idea is that the state of a component would automatically be exposed as CSS custom properties.

<template>
  <div class="text">Hello</div>
</template>

<script>
export default {
  data() {
    return {
      color: 'red'
    }
  }
}
</script>

<style vars="{ color }">
.text {
  color: var(--color);
}
</style>

By virtue of having color as part of the state of this component, then --color is available as state to the CSS of this component. I think that’s a great idea.

What if every time you used useState in React, CSS custom properties were put on the :root and were updated automatically. For example, if you did this:

import React, { useState } from 'https://cdn.skypack.dev/[email protected]^16.13.1';
import ReactDOM from 'https://cdn.skypack.dev/[email protected]^16.13.1';

const App = () => {
  const [ activeColor, setActiveColor ] = useState("red");
  return(
    <div className="box">
      <h1>Active Color: {activeColor}</h1>
      <button onClick={() => {setActiveColor("red")}}>red</button>
      <button onClick={() => {setActiveColor("blue")}}>blue</button>
    </div>
  );
}

ReactDOM.render(<App />,
document.getElementById("root"))

And you knew you could do like:

.box {
  border-color: 2px solid var(--activeColor);
}

Because the state automatically mapped itself to a custom property. Someone should make a useStateWithCustomProperties hook or something to do that. #freeidea

Libraries like React and Vue are for building UI. I think it makes a lot of sense that the state that they manage is automatically exposed to CSS.

Could browsers give us more page state as environment variables?

Speaking of state that CSS should know about, I’ve seen quite a few demos that do fun stuff by mapping over things, like the current mouse position or scroll position, over to CSS. I don’t think it’s entirely unreasonable to ask for that data to be natively exposed to CSS. We already have the concept of environment variables, like env(safe-area-inset-top), and I could see that being used to expose page state, like env(page-scroll-percentage) or env(mouseY).