CSS custom properties as a delivery channel
James Stanley put together a seasonal stylesheet whose custom properties hold colors that shift day by day — greens in spring, oranges in fall — so a site can be themed against values that drift slightly. The catch, as I see it, is that the file also ships opinionated rules like the body background; a version exposing only the custom properties would let you apply the colors however you like, with no side effects.
: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%);
}
That raises a broader thought: a CDN-hosted CSS file could carry other time-sensitive data — today's date for pseudo content, the phase of the moon, sports scores. Pseudo content counts as accessible content now, though its text can't be selected, so this isn't an endorsement of CSS as a content API. It's just a data API that's trivially easy to consume.
/* <div class="soup">The soup of the day is: </div> */
.soup::after {
content: var(--soupOfTheDay); /* lol kinda */
}
What a custom property can hold
Will Boyd wrote up what's possible to put in a custom property: nearly anything is a valid value, and usage generally behaves the way you'd expect.
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 picked up on that flexibility, listing the tricks it enables:
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)
He also highlighted the "basic" state-flipping a single 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))
);
}
Layer on more complexity and you reach The Raven, which brings media queries into custom properties. I'd like to see CSS itself make this easier. Media queries earn their keep now, and container queries will too, but generic state would compound that value — you could apply arbitrary styles whenever the UI sits in an arbitrary state.
/* 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;
}
}
Bram wrote about that future as well, citing what Lea Verou calls "higher level custom properties".
Tooling that pumps state into CSS
A component's own state could be surfaced to its stylesheet automatically, which is the idea behind this Vue proposal (status unclear).
<template>
<div class="text">Hello</div>
</template>
<script>
export default {
data() {
return {
color: 'red'
}
}
}
</script>
<style vars="{ color }">
.text {
color: var(--color);
}
</style>
Since color belongs to the component's state, --color is available to that component's CSS. Extend it: every useState call in React could drop custom properties on :root and keep them updated.
import React, { useState } from 'https://cdn.skypack.dev/react@^16.13.1';
import ReactDOM from 'https://cdn.skypack.dev/react-dom@^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"))
Writes like this would then just work, because the state mapped itself onto a custom property:
.box {
border-color: 2px solid var(--activeColor);
}
Which is an argument for a hook like useStateWithCustomProperties. #freeidea React and Vue exist to build UI, so it makes sense for the state they manage to be exposed to CSS by default.
Page state as environment variables
On the same theme, plenty of demos feed things like mouse position or scroll offset into CSS by mapping over them. Asking the browser to expose that natively isn't unreasonable: environment variables already exist in the form of env(safe-area-inset-top), and page state could ride the same channel as env(page-scroll-percentage) or env(mouseY).
Naming note
Calling these "CSS variables" is common and understandable, but Šime Vidas noted the accurate split from the spec: --this-part is the custom property, and var(--this-part) is the variable.



