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/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"))
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)
.
This looks very nice indeed! I would like to add if you use Sass; it rounds down decimal numbers when processing things for the sake of speed. If you need specifically long and precise decimal numbers, like for hsl, please resort to the Sass-docs on how to set –precision for those.
That sounds like polluted
window
object in JS with global variables out of any scope. Wouldn’t it be one big mess once again?You could scope the variables to the components root. Or not.
rgb(var(--rgb) / var(--a))
Hi Chris, nice article as so many times before!
I was puzzled by this part, is it expected to make the body background transparent? It did not work in my test, and I have never seen it before.
See dis: https://css-tricks.com/no-comma-color-functions-in-css/
Yup, env variables will be really helpful. I hope it becomes a thing…
I would be very surprised if I’m the first to do this, but I went ahead and built the custom hook you mentioned. The hook allows you to inject your react state into CSS variables. :)
https://www.npmjs.com/package/use-state-in-custom-properties
Pretty sweet I think!
I actually had to do this once – control css custom properties with
useState
I just added the custom property to the style attribute and was able to capture the value in my css.
For simple styling, that would be fine, but for pseudo content, I wouldn’t recommend it for accessibility reasons. Not all screen readers can access that content, and non-decorative content added in this manner is considered a failure of WCAG guideline 1.3.1 (https://www.w3.org/WAI/WCAG21/Techniques/failures/F87 )