 Hi, and welcome to the CSS podcast. This season, we're answering your most burning CSS questions. And today, we're covering a very common CSS issue, which is, why isn't my custom property the value that I expect? CSS variables seem straightforward on the surface, but there are times that their behavior may surprise you. And it could just drive you crazy if you run into that. It's in these moments that it's good to know how the browser computes these custom property values and why you're running into those issues. Excellent. Yes, this is especially important as more and more complexity is moved in the orchestration of custom properties. Our design systems are getting crazy in there. It's awesome. They are. Yeah. So many layers. Although the Almanac did that search, and it said most people only do a flat set of custom properties, I bet you this year, we're going to see the depths going up to three or four or five nested custom properties. Well, anyway, today's episode will help you prevent surprises and help the health of the design system. The types of problems you have with custom properties can be pretty quickly listed, but explaining them takes a little bit more work. But that's why you're listening to the show, right? Anyway, here's a quick overview of the common problems you might run into with custom properties. They might be undefined. They might be wrong. They might be invalid. And they might be invalid at compute time. Ew, compute time. That does not sound like a friendly term to learn. Those all are just different words that are saying they're incorrect, unexpected. But there's different reasons that they might be unexpected. And there's also a few different reasons why you'd run up against some of those custom property value issues. And those reasons vary, too. First, you might be misunderstanding where the value is resolving in the CSS cascade. You might also be confusing different types of values, like declared, cascaded, specified, computed, used, and actual values. And there's also different times, like parse time, compute time, and use time from when you're actually leveraging those custom property values. And when the browser is reading and interpreting them. So there's lots of stuff going on with custom properties. It's true. There's a lot of value types. And you'd think it's like it's holding a string. Just make it simple. And you're like, ew, you'll hear it. It can get pretty complex pretty quick. But if you need a review of custom properties, we do have episode 46 for a nice recap of some of this stuff. And as a quick summary of that episode and just the different ways that you can create custom property values, it's essentially three ways. And we're not even going to get into all the complexity of these ways. We're going to mostly going to talk about string-based custom property values, because those you run into a lot of issues with, even on their own. But there is the first way, which is just creating custom property values with that double dash prefix and giving that custom property a value. This would be a string-based custom property value. You can do this in CSS. You could do it inline. You could do it through JavaScript if you wanted to. So there's a lot of ways to do this. Then there's also registered custom properties. So you can use app property to give the custom property a name, syntax, default value, and specify if it inherits or not from its parent. So even within that, there's a load of complexity. You might specify that it doesn't inherit. And then you expect inheritance. And then be confused on why that's happening. And then finally, you can do the same thing with registered custom properties in JavaScript by using CSS.registered property, which is a static method that lets you define a custom property with the attributes that I just mentioned. So you might have custom properties being registered in different places. And you might have overlap there. Well, if you're not confused enough yet, we've got to break down the lifecycle of the resolution of a custom property. And we're going to hopefully make the spec a little easier to understand. But Yuna listed off the different value types earlier. And we'll give a kind of brief definition to those right now. So why don't you start with our first value type for custom property? Yeah, sure. So the first value type are just declared values. And that is what you, as a developer, author in your style sheet directly or in line with the element that you're applying the style to. So next, the browser finds the cascaded value for this. And it's going to choose the winning one for that value that wrote, right? You could have it written five times in five different locations. It has to use the cascade to decide which one it's actually going to move forward with. So then you might have a specified value, which are often the cascaded winning value. But if there's no cascaded value, then it's the defaulted value. Then next, we have computed values. They use the specified value and then resolve it. It's an abstract data representation, given the context and the surrounding declarations that the browser has to help resolve this. This could be like determining what two REM actually is or the value of the keyword bolder, like what does bolder actually mean? What does two REM mean? So any relative value is computed here. This is also true for relative URLs, which we'll cover later. Yeah, I like those examples there. So you may also have a used value. And while every element has a computed value, like Adam just mentioned, not every element will have a used or actual value. But you could have a used value if you have any remaining calculations to be done. So the used value will take the computed value and it completes any additional calculations to make it an absolute value. So it'll be the outcome, the used value of a computed calculation. So if you were using REMs, for example, in a calc, you still have to adjust the calculations or if you're using a relative unit like a percentage in a calc, you still have adjustments to be made to get that specific, absolute value that you're applying. And then the last one here is the actual value. And these are the used values. So most of these values, as we went through them, they used the previous one that came before it. So the actual value uses the used value, which used that computed value anyway. And these are after all the adjustments are made and this last final process of actual value probes the used value and uses as much from the document as possible to determine the actual value. So it's almost like a final pass. Like, are we sure this is the value? Cause this is going to be the actual value we send around to the UI and they're like, yeah, let's do a final probe. Anyway, so that's called the actual value. So now we talked about all these values. We talked about the different ways that you might write custom properties, different ways that you might define errors from custom properties. Let's take a look at a real scenario. A really common scenario for custom property frustrations is, say you have all of your custom properties in your root scope. So an example would be, you have your root and then in that you set color primary, say it's red. And then you have custom properties that rely on other custom property values or maybe a calc or something else in there, like color button primary. And you might set color button primary to var color primary. So now you're referencing color primary in the root from color button primary. And that's okay. So if you use color button primary, you will get red. If you use that on the button, you'll get red as expected. But then if you wanted to create a modifier like blue button, like say you have a class of blue button somewhere in the file and then you update color primary to blue, now if you call color button primary in blue button, expecting the button to be blue, it isn't. It is actually red and it's out of scope. It doesn't actually update that root scope and then go back down. The problem here is that you're expecting a child selector scope to update a parent scope. So you're assuming that custom properties are fully reactive in this way, whether or not. And there's actually a CSS tricks blog post on this called the big gotcha with custom properties, which we linked in the show notes. And fortunately, there are a few ways around the issue and there are three resolutions that actually change where the used value is, which is what you want to be referencing and updating here because you have a reference of that custom property. Yes, excellent, excellent explanation. I almost combined those words together. Explanation, the excellent D thing. I don't know. Anyway, this first solution though to this particular problem, which also comes in all the times like opacity. Sometimes you're like, Hey, I changed the opacity variable, but my color is not half opaque. Like what's the deal? And so the first solution here is to scope the variable. So in this term, in this example, the variable we're talking about is color button primary. The one that we wanted to switch to blue in a child context, you want to scope it to where you're using it. So where it was previously defined in root as color button primary referencing var color primary, we could scoot that entire definition down into button. And then when your variant changes the custom property of color primary, that is within the scope of where it's being used. So you're changing the used value to where it's actually being located as opposed to trying to put things up too high in the root scope. And so this manifests like, instead of expecting a scoped to root custom property update, you're just defining it at that nested part. So that's the crucial part here is you need to define it at the nested part, define the reactive one that wasn't reacting like you thought. And so you have a selector with a button called like half opacity brand. You use props from root, but then as each of these buttons need variants, you have to redefine them so that they get updated. It's that computed versus used value issue. And so that's how you get around it. Yeah, that's probably the solution that I would choose also is just to change where that scope is. But another thing that you can do is scope this in multiple places. So instead of just applying these properties in the root, you could do colon root comma button and then have the same custom property values there. So this is kind of funky, but it fixes the problem and it makes the initial definition get a bit fuzzy, but it is now scoped in two places so you can access it and you can trace it. I don't know if this is going to work for everything, but if it's something that is specifically related to button that you also wanna be available in the global root, this might be something that you do. I wouldn't do this for all of your custom properties. Like you don't wanna have like colon root comma button comma P comma, I don't know, div. That's probably gonna get too crazy. But if it is something specific, like in that example, this can work as a solution and it will fix the problem. Yeah, I agree. I'm not gonna use this solution, especially like it's just kind of, it's funny. The name of the custom property has button in it. It's almost like you knew the whole time you should have defined this at the button level and not the root level, but you're trying to put stuff open. Have global variables. Have some global button variables. You're like, hey, maybe a card background wants to be the same colors of button. I don't know. Anyway. And that's a good call. That's probably the best place to put this and to organize your structure is to have your global values in the root. But then if anything is specific to all of one type of element, it might be better to have those definitions reference the root above, but be defined in the same scope as the other elements that will be using those references. Yeah. The last one here. So you don't mention putting a comma in your selector. So it's colon root, then your context that's your nested child context. Or you can just put them on star. And you see this happen all the time. Tailwind puts a bunch of things on star. Open props is gonna put things on star for like shadows that way. Every time you use a shadow, you can tint it to a different color. So the only thing to be aware of here is that there are some performance implications. You can't put too many things on star because you're literally redeclaring every custom property for every single element over and over again. And that's just asking a lot out of the browser, especially if you have a lot of nodes in your document. Yes. The number of nodes is a huge player here. So keep that in mind, especially if you're working on like a site that server renders a lot of content, the nodes will grow as content is rendered on the site and it might affect performance of styles that you might not make that connection because you wrote them one time and then you have your DOM separately and if that DOM grows or is an unknown size, all the CSS problems grow exponentially with DOM size. So something to look out for. Another scenario that you might run into is when you're trying to concatenate custom properties with units or using unit list custom properties like in a calc function. So say you have as an example the following custom properties. So you have a value custom property, so dash dash value and it's two or some numeric value. And then say you have a unit like dash dash unit and it's REM. So you might try to do something like font size, if you had a calc, var dash dash value plus var dash dash unit, but this doesn't concatenate to two REMs. It just doesn't work like that. Even if you're thinking that these are both string types, calc just doesn't work that way. However, you could change the value of unit from just REM to a value like one REM, two REM, three REM, like an actual value. So if you wanted to do something like a converter, you could do like font size, calc and var dash dash value times your one REM or your unit being one REM. That will result in two REMs. So you can kind of like create many functions this way. You do have to keep in mind that with REM, you still have to have your computed value and then the actual used value is gonna be updated with this calculation. So another example where we go back to that life cycle of custom properties, but just something that I've seen people doing is trying to concatenate custom properties. You can't just add them like a JavaScript template literal string. You do have to have some kind of value that you're combining, calculating together. Yeah, this is happening to me too with like durations. I'll have like a duration as like a unit and then I'll try to just append. Like seconds. Seconds or milliseconds to it. And then I'm like, it's not working. So yeah, that's why you'll see in style sheets all the time you multiply by one second to convert the unit list thing into time, which is cool, excellent explanation. Our last scenario, so this one is, I wouldn't call this a gotcha, but this is a very seldom known feature of custom properties is when you're using URLs in your custom property. So a relative URL and a custom property resolves based on where the file is located that originated that URL custom property. So this might not ever be an issue if you're working with local files, but once you put the files that have a bunch of URL props on a CDN, you're in for a surprise. So let's say you have a CSS file with some icons in it, you know, SVG icons and the URLs are relative. That CSS file is in your local project folder next to some assets or whatever and all as well. But as soon as you put that file on a CDN or maybe it's been moved or flattened because CDNs like to flatten folder architectures, the paths will break. However, you could also use this to your advantage. So it's not always just gonna surprise you. You could host a file and do this intentionally. So host your relative URL props on a CDN as they will resolve to the CDN directory and you can pull from an adjacent folder intentionally. Like imagine you import a CSS file of icons from a CDN and it uses relative URLs and also hosts the icons. So that way that's very handy. You don't need to adjust any of your URLs to get the icons right in your CSS. That relative URL feature becomes very handy. Check out the show notes for an example of this from Romain Menke from Post-CSS. They have a, they pretty much made a test for it because it's a feature of relative URL custom properties but it also shows you a cool use case. Yeah, that's a really cool idea. I hadn't really used URLs for custom properties in the past but maybe I will because that's like a neat solution. So to wrap up this show, a couple of takeaways for you. Hopefully you learned something a bit more about custom properties and dove a little bit deeper. The first takeaway is that values can be inherited by descendants but those descendants can't pass back values to their ancestors. Yeah, that's a big one. Here's another takeaway. An invalid custom property value is unable to fall back to previously set cascaded value. So an invalid custom property is invalid and without a proper fallback. Yeah, anyway, it surprises people. Which is also why I really like registered custom properties because you can set the fallback so you don't run into that case and you kind of like foolproof your custom property systems which are a big part of design systems. So like you're sort of like type checking them in a way. Also just learned all about URLs and custom properties. So relative URLs are resolved to the directory they are found which can be a remote directory, a neat tip. And then here, you already captured this takeaway but use a fallback value when defining a custom property. That way in case something happens there's always a value there to be found. Yes, yeah. Also using at support something that you can do to not break those fallback values. So that's something in terms of browser support at property is almost stable. It is so close. I'm pretty sure it will be right into the year. It's part of interop. It's in two of the modern browsers and it's like ready to ship in the third. So hopefully by the time that we get out of this year we won't have to worry about using at supports. But if your browser matrix of course is a little bit longer term then just use at supports. I'm ready. I'm ready for it. Yeah, me too. I've wanted that so long but I can see it says it just feels like such a missing, huge feature need. Another thing to note is we as a Chrome team are currently working on custom property debugging. So like tooling to improve custom property workflows right now. So if you have ideas or are running into scenarios where you need more support, give us a shout. We would love to hear from you and hear about the things that you're running into when it comes to custom properties and issues with them and debugging with them. That's all we got for today. So thanks for joining us. Yo, if you have any questions about custom props or whatever, we'd love to answer them on the show. Just tweet us with the hashtag CSS podcast. I'm Yuna, that's at UNA. I'm at Argyle link ARG YLE INK. Check the show notes too. There's a lot of goodies in there. But yeah, ask us a question. We want to hear it. Yeah, good call. We're gonna have a bunch of links in today's show. So check them out. And if you like the show, please give us a review on whatever podcast app you're using or share this podcast with a friend because those reviews help other people discover our show. And that means that we get more time to focus on making more for you. Yay, thanks y'all. Looking forward to your questions. Bye, we'll see you next time.