Lightning in a bottle with CSS custom properties

Adam Argyle
Adam Argyle

This insightful lightning talk from Adam Argyle delves into CSS custom properties in modern web development. The central theme of the discussion revolves around the use of these properties - or "variables in a bottle" - for developing dynamic themes and enhancing type-safety.

Adam discusses the creation of a comprehensive theming system based on a single color, with derived properties modifying this base color for a consistent and easily adaptable theme across applications. Crucially, features like dark mode implementation become much easier with CSS custom properties.

One of the highlighted challenges is the loosely typed nature of custom CSS properties, which can lead to systemic issues in development. Adam proposes the use of type-checking tools (TypeScript) coupled with the CSS @property feature, to create typed custom properties and avoid these pitfalls.

On the topic of embracing progressive enhancement, the session highlights the successful adoption of @property by browsers like Chrome and Safari. One practical application discussed is its use for type guarding in Tailwind 4.

The discussion culminated with a look into the future of CSS custom properties, with Adam speculating on new functionalities and inviting conversation about upcoming specifications for typed CSS variables. Adam emphasizes the power of CSS and encourages developers to harness its full capabilities, such as mathematical functions and scroll-driven animations, for enhanced web development.

Share this talk with your friends

Transcript

Alright, I'm giving the first lightning talk of the day.

This is going to be a five-minute whirl-wham-bam tour into what you can put inside of a bottle. I'm calling a bottle a variable. Right? It's a container. It's a satchel. It's a container with instructions for what's inside. But it's called lighting the bottle because we are going to be talking about, ooh, allow accessory to connect.

Yeah, I would like that. Don't trust that. I should have hit no. Was there, oh, so the HDMI was more reliable here? My computer has both. Okay. Come on, victory. Victory with the HDMI.

Hey! Lightning in a bottle. I used AI to make a bottle, and then I made a gradient vignette on there. Anyway, okay, so lighting in a bottle, and I'm going to leave the URL visible so that you can see that they're full-page navigations, which is kind of cool, even though they morph. If you read Kent's article, he was talking about view transitions in the beginning of

it, something the MPA couldn't do, but the PESPA could. Okay, whatever. The goal today is to show you what you can do with custom properties, specifically how to guard CSS systems with type-safe property variables. Did you know CSS is typed, and that you can write your own types, and you can make your systems types? You're going to love this, then.

So okay, just a quick review. Oftentimes, you use props like a global state store, right? You've got a root, which is pretty much the highest level place you can put a prop, then you use it somewhere else. Very easy and simple to understand, and what's cool about them is they can hold like anything. Look, you can delete all the, you know, put some SQL injection in there. You can put all your padding values.

You can hold booleans, math, gradients, icons. It's all there for you. You could put entire scripts in there. People have put these amazing things people have put into queries, but the type is unknown. It's such a loose-typed variable that it doesn't know what it is. It anticipates not knowing. It anticipates you being able to put whatever you want inside of it, and that kind of becomes

a problem that we're going to find out here in a second. Let's do the next level. So instead of just a global state store, we're going to make derived or computed props, right? So we've got a prop with a prop, so we've got brand, and now we're making a brand color. We're going to modify it so it's a little bit lighter by using color mix, specifying our brand, and passing white. It's a function that takes a parameter.

Kind of a cool way to think about it, or a derived state store. So then we want to go crazy. We want to build a system out of this. We want one color that can change our entire theme, right? That's really rad. So we've got a brand highlight, slightly lighter, a brand shadow, slightly darker, mixing with black. Then we make a gradient using those props. We have props and props and props, and it's a really great reactive system.

It's nearly a reactive system as much as you think it is, like RxJS or values in Svelte that just sort of magically update. It's very, very similar. But we wanted to add a dark mode, so here at the bottom we say prefers color scheme root and oh no, I made the color light stink. That's not a real color. CSS even knows.

It goes light stink. That's nothing. And then my whole system breaks because all my functions that depended on that previous value are now all wrong. They're all trying to mix light stink with black and white. That's gross. Anyway, that's the problem that you'll run into. Someone can accidentally, this is why we like TypeScript, someone can accidentally break an entire system with a typo or a bad value.

So let's fix it. This is the hero slide of the day. If you take one thing away from this quick lightning talk, it is enter at property. Turn brand into a typed color variable through some simple syntax. You say I want to make a new typed custom property called brand. Here's the syntax. It's a color type, and you can choose inherits or not.

And an initial value, meaning that it's guaranteed valid. Even if you try to force a bad value into that brand custom property, it doesn't care. It has a valid value to always fall back to. It's resilient and strong and tight. So altogether, our new updated version is we have our root and star.

So notice the selector was changed from root to root star, and that's so that these things will continually recompute their value at any level in your nested DOM. And then we use CSS nesting in there to just tweak that in a dark theme. What's nice, though, is in the dark theme, our dark theme is not broken anymore. Our light theme persists because the typed custom property won't even allow itself to

get into a broken state. So the type guarded brand variable prevents that system doing it. There's even a code pen link in the bottom here, and we can go check it out. You can go pass bad values, good values to brand and watch the theme of the button change that I put in there. Here's all the different CSS types we have. We have length, which is like pixels. We have numbers. We have percentages.

We have length percentage, which means it can be a length or a percentage. So it's a dynamic type made up of other types. We have color type. The color type is made up of multiple types as well. If you go look at the CSS specs, it's spec'd from day one, top to bottom. It's a very typed system. It's very cool. Anyway, you have images, URLs, integers, angles. My favorite angle is the rad. You can have a two rad.

How many turns are you going to give it? Two rad. Anyway, whatever. You get time, resolution. Time is like how long is the duration of your animation? One second? Five seconds? 300 milliseconds? You got resolution, transform list, and transform functions, and you can even make custom types.

When you defined that app property, you can say it can be any of these types, and then include even a custom type, which is keywords that you define, meaning that the design system has keywords that are meaningful to you and that you trickle through the entire system. It's super rad. You're asking yourself, but can I use it? Oh, yeah.

It's in Chrome, and Safari has been, Firefox has been working on it for a long time, and they just finished the animatable part of app property, so there's a whole bunch of other reasons to use app property other than typeguarding your systems, but it's coming in June. You could also progressively enhance to it. If you use Tailwind 4, it's behind the scenes.

You don't even know it, but they've typeguarded their own Tailwind system in Tailwind 4 with app property under the hood, so you're going to start seeing it out in the wild. As just a little hook as we watch that animation again, because it's fun, one more time? No. Okay. Yep. If you want to learn more about CSS custom properties, come see me after the show.

If you think custom props can do more or you want them to do more, we're working on ... There's a spec for typed mixins. There's a spec for typed CSS custom functions, and the syntax is really cool. Your parameters in the function are the types that you expect, and then you get ... It's just like in TypeScript. It looks a lot like TypeScript. You can make games with custom properties. You can do triggers.

You can do new animation capabilities with app property because since you typed the property, it's no longer unknown, and it knows how to interpolate from a percentage. If you say the percentage is zero to ... Otherwise, it just thought the percentage was just like a string that you could change at any point, but when you type it, it knows for certain that it can go from zero to some other value of that custom property and over time.

You can teach it to interpolate things that it couldn't before. You can make switches. There's all the math functions you could want. We saw scroll-driven animation functions. There's so many functions in CSS, and there's so much more. If that stuff sounded interesting, if you're interested in type-safety, your custom property or your design systems, and you want to know more about this stuff, come see me, and there's the slide link down there at the bottom. Lightning in a bottle.

Whoosh, whoosh. Woo!

Related Talks