Is Sass Dead Yet? CSS Mixins & Functions &c.

Introduction and Speaker Welcome

Stewart Hay introduces Miriam Suzanne, highlighting her contributions to CSS education and layout techniques. He praises her ability to balance theory and practice in teaching and jokingly mentions her request for him to deliver her talk. Miriam is welcomed to the stage to discuss her insights on CSS advancements.

Evolution of CSS and Complexity

Miriam Suzanne reflects on the evolution of CSS, noting how it has transitioned from simpler times to a more complex system with features like custom properties and grid systems. She discusses the challenges of maintaining meaningful abstractions in CSS and the role of tools like Sass in simplifying code without obstructing the base language. The segment sets the stage for exploring new CSS features.

Introducing CSS Mixins and Functions

Miriam Suzanne introduces the concept of Mixins and Functions in CSS, explaining their potential to manage complexity through abstraction. She discusses the current state of browser support and the need for community feedback on these new specifications. This segment emphasizes the importance of abstractions in managing CSS complexity and previews upcoming demonstrations.

Demonstrating CSS Functions

Miriam Suzanne demonstrates the use of CSS Functions, showcasing their syntax and capabilities. She explains how functions can contain conditions like media queries and highlights their potential to simplify repetitive calculations. The demonstration includes examples of using functions to manage CSS properties dynamically, illustrating their practical applications.

Exploring CSS Mixins

Miriam Suzanne explores CSS Mixins, discussing their syntax and current limitations due to early-stage browser support. She demonstrates how Mixins can encapsulate styles and media queries, simplifying the application of complex layouts. This segment provides insights into the potential of Mixins to streamline CSS code organization.

Understanding CSS Variables and Inheritance

Miriam Suzanne delves into the intricacies of CSS variables and their inheritance behavior. She contrasts CSS variables with Sass variables, highlighting their different scopes and inheritance patterns. This segment explains the significance of understanding variable resolution in CSS and its impact on styling consistency.

Cascading and Defaulting in CSS

Miriam Suzanne discusses the cascading and defaulting processes in CSS, explaining how the browser resolves conflicts and fills in missing values. She emphasizes the importance of understanding these processes to effectively manage CSS styles. This segment provides a deeper understanding of how CSS properties are applied and inherited across elements.

Advanced CSS Features and Functionality

Miriam Suzanne highlights advanced CSS features, including typed properties and the use of functions to manage complex styling scenarios. She discusses the potential for functions to provide parameterized variables and the importance of understanding CSS's declarative nature. This segment showcases the evolving capabilities of CSS in handling sophisticated design requirements.

CSS Limitations and Future Directions

Miriam Suzanne addresses the limitations of CSS as a declarative language, noting the absence of control flow features like loops. She discusses the ongoing need for tools like Sass for certain use cases and emphasizes the importance of community feedback in shaping CSS's future. This segment concludes with a reflection on the balance between CSS's capabilities and its inherent constraints.

Q&A and Closing Remarks

Stewart Hay and Miriam Suzanne engage in a brief Q&A session, addressing audience questions about CSS typing and function naming conventions. Miriam explains the rationale behind certain design choices in CSS and highlights the importance of understanding CSS's declarative nature. The session concludes with gratitude for Miriam's contributions to the web community.

The thing I really appreciate about Miriam - next to container queries - which I don't wanna, I don't wanna, throw that out to the side as if it's trivial, it's very important.

But even more important, I think is what she's done to teach people, because there are teachers who dive deep into the theory of something, there are teachers that are very, very practical and there are teachers who kind of sit in the sweet spot between the theory and the practice.

And I feel that Miriam's done that before.

She's also done a lot with my favorite topic in CSS, which is Layout.

And I'm really, happy to have her here today.

I asked, her, is there anything you want me to say?

And she said, "Could you do my talk for me?" And then she said, "I'm looking for work", which I don't quite believe, but then you know that.

Please welcome Miriam Suzanne.

Hi, thank you.

I will not be as practical today.

So, ah let's wait for this to turn on, Yeah - is it?

Yeah.

No.

Oh, yes, okay!

How many of you wrote CSS before there was nesting in CSS?

Yeah I'm guessing that's most of the room, right?

Baseline 2023.

How about before color mix?

This is a trick question, it's the same time.

So same people.

Okay, before custom properties?

Yeah - a lot of you, let's skip way ahead, how many of you are old?

Yeah, okay.

So things used to be simpler on the Web, there was no deep nesting, no worry about color spaces, no variables in CSS no build steps really.

Things used to be really simple as John showed you - the early browsers, we didn't do any styling from our end, the browser did all the styling, but things were also more complicated, right?

We got Table Layouts, and we were in Photoshop, slicing up images, what a mess.

And then, oh my goodness.

The Layout libraries, the grid systems, like what is even going on here?

What is that selector supposed to mean?

Why are we floating it?

Why display in inline, everything that's floated is blockified, what's happening?

What are those numbers?

The first talk I ever gave back in 2012 - was called meaningful CSS, and it was about these problems - that was actually a demo from that talk.

And I was talking about the lack of meaningful abstractions in CSS and how that leads to some complexity, even in a simple language.

Repetition is hard to maintain.

Relationships that should be obvious are hidden and sometimes the reasons for a style are unclear and we want to provide more meaning in CSS.

How can we do that?

Oh look, I have another slide with the same words.

I also - I talked some about the Dao - thank you so much, John.

What a wonderful article, I've been referencing it ever since.

What an honor to try to follow you.

And I talked about some specifications that were coming that would give us a little bit more abstraction, a little bit more ability to show relationships, show intent behind a value.

We could take one of those confusing numbers and we could give it a name and we could say, this is why this number - so it's not quite as magical of a number.

And I also talked a little bit about Sass and, how Sass could also bring some abstractions, without getting in the way.

This is a thing when I'm thinking about choosing a tool that I'm gonna use in my code.

The most important thing to me is that I can still use the base language.

That's it, that's the main decision that I'm making.

So if I'm using a tool on top of CSS and I have to wait until that tool adds support for CSS features, that's not a tool, that's an obstacle.

so Sass was my choice because it got out of the way.

If I wanted to use CSS I'd just use CSS.

But it let me take that chunk of 'what is a span?' And I could just simplify it down to I want this outcome, and give me that chunk of code.

So a simple language can have complexities to it.

Like it, the code can be complex because the language is simple and vice versa.

Some language complications can make code simpler.

So complexity is this moving target.

And a few years later, CSS got grids, and we got basically that same syntax and then some better syntax, even it's even more explicit with a grid syntax, what the relationships are, what the meaning- what the intent is.

So that was excellent.

And then I had to do an apology tour telling you to stop using the Sass grid systems and - but you can do that - you can pull it out and, and use the main thing.

It's great when we - this complexity is a moving target, and sometimes we might use external tools to help deal with it and sometimes we might get new tools in the language to deal with it, but there's complexity that happens in what we're trying to achieve.

And we can chase it around.

We can't make it go away, but we can chase it around, and it's something that we have to manage.

So, we're looking for abstractions that will help us manage it, but then we also do have to manage it if we have that complexity there, right?

So we can move things away into a function or a mix in.

They're still complicated, but now they're complicated over here.

And that's sort of what the abstraction gives us, and it's a trade off.

It's like, we've got this complexity, can we just get rid of the complexity or can we at least manage it?

So, abstractions make everything better and abstractions make everything worse, so we're gonna talk about them.

I'm back on my bullshit, we've got some new abstractions!

So, now Mixins and Functions are coming to CSS.

There's a specification, first public working draft fairly recently, and we're going to play with them a little bit...

as you can see, the support is excellent.

There is a Function rule and you can't use it anywhere.

And there's a Mixin rule, and it is so new that the support is unknown.

We don't even - we don't even know what that is to tell you that it doesn't work.

So this is going to be a very practical talk.

But we need feedback, we need feedback on the specifications.

There are prototypes of both things in Chrome, so I will be able to show you some demos, and then when you're - if you're ever like wanting to leave feedback for the CSS working group, you can do that.

Let's just go over there and we want to deal with Mixins.

Oh, I probably should zoom in on this.

Can you, somewhat see that?

So I would just go to - these are in the same spec, so Functions and Mixins are both in this spec - and you can filter down to just the issues that have been filed around that.

And it's not a lot so far, you can add more!

Feedback is essential in this process and it's often hard for us to get feedback before shipping the thing, and then once you ship it, you can't change it, so you start to get feedback and now it's too late.

So the earlier you can get in there and give some feedback, the better.

Let's play with some Functions.

Here is a very simple function, the most basic function you can write, where we've got an @function rule.

Should I go even bigger on this?

How are you in the back?

Can I get a thumbs up or a thumbs down?

Thumbs ups.

Great.

So we've got an @function rule.

We've given the function a name- we could name that anything we want... this could be times-two, we could call it something else.

We have - I don't know why I called this the first parameter - it's the only one at this point, so we're accepting a value, and then we're returning a result.

We get a result at the end.

And then down here I'm calling it - it's sort of like a variable, it works wherever variables would work, but we're not putting it inside of the variable, syntax.

We just call the function, and we can pass variables into it.

So we wouldn't have to... we could say, just pass in a number.

It wants a length, so 1em or 12 pixels.

And, they're both getting the same one, right?

I am applying that double to both, so I don't know why I have two separate divs there that act like they're doing different things, they're doing the same thing.

But it is doubling.

It's not giving me, it's not giving me the two pixels that I asked for, it's giving me four because it's doubling.

We could have it more than double, we would want to change the name of it.

So we're just taking a calculation, we're pulling it out, we're putting it in a function, we can pass values into the function.

We could now say we want a second value, and we'll use that second value... and these.

Oh, I didn't pass one in, so I could pass one in, or I could make there be a default... I guess this would be the multiplier - let's call it something like that.

So the multiplier is one or three, but then we can also pass in a multiplier here, and that will override the default that we set.

Code Pen wants me to save.

Sorry I didn't save Chris!

This is - we're dealing with - these are not exactly properties on the elements, so if I come out here and I say, "actually, I want the margin to be that value that I passed in", right?

So I'm gonna say variable -value, and I just want to use that - I didn't get anything.

That value is only defined- that parameter is defined in the function - it's not available outside.

But we could say...

let's see, we've got this gap variable, and we can say here that I want the value to be - well either I'll take the one that's passed in, or I'll use the gap.

Alright?

I don't know if I want to override value there, we'll call this... we'll call this fallback or something.

Now, at this point that's not working because, the value doesn't have... because the value doesn't have an initial - it's a required... it's a required thing to pass in - it's a required argument, so I have to pass something in there.

But we could make it optional by just giving it an initial.

So that's - it doesn't have a value, but it's also not required.

Fallback is used here in our calculation now, and we can see that this gap value is getting passed in.

So the function does have access to the context where it's called, it has access to custom properties that are defined in that context, but the context does not have access to the internal, local, variables or we don't have access to fallback outside, we don't have access to the parameters outside.

The other thing you should know here is we're dealing with properties.

So let's say we take, let's just change this to 1em.

Yes, thank you.

We can see the second one is taking precedence.

We're not dealing with - in Sass, we would be dealing with the order of things.

They would stack up; we could take a variable and we could do things to it and have that variable again.

Here we're just dealing with properties in a bucket.

This is a declarative language.

So like properties on an element, if we have multiple of them, and it doesn't matter - whoop - otherwise the order of these things.

I can move the result to the top, these are all just properties in a bucket.

The order is not important between properties - it is important within a property.

So that declarative versus imperative change is gonna be a little bit different from anything you've played with in Sass functions.

So one of the common use cases that people have for just "oh, a calculation that I have to do a lot and I'm just writing this calculation over and over, and maybe it would be nice to have an abstraction where I just say what I'm doing", is the negative function.

I just want to take a value and I just want to make it negative, so I can pass in an amount, and rotate it.

So this amount is getting passed into both, and one of the divs is turning it negative.

great.

Just a small use case.

So functions start with the function rule, and then we give the function a name and it has to have a dashed identifier because CSS already has built in functions that don't have the two dashes so we need to distinguish between them - so we can name it whatever we want, we can set up some parameters, we can give a result.

And the result similarly, we could result - we could have multiple results.

And the last one that happens will... will be the one.

So the result is sort of doing that...

cas - well it's not cascading, but

order of appearance, the final one will take precedence, similar to properties.

And then we can have any declarative logic That we want inside of there.

Ah - functions act like variables, we can call them where we want them, but they have their own little syntax.

And like I said, a bucket of unordered properties - all the stuff is just in there.

We're not doing a controlled stepping through each step.

So, external variables are available inside parameters and internal variables are private to the function, and then only the result is visible from the outside.

One of the cool things about these functions that Sass can't do is that these functions can contain conditions.

So conditions like Media.

So here we've got a (prefers-color-scheme: dark) and outside we have the result: light, but if (prefers-color-scheme: dark), the result: dark.

And then we're just calling this 'user-scheme', and we get the output.

I guess I would need to... if there's some way here to say: "I prefer light", and we can't see that output because...

yeah, so now we're responding to

the color scheme using a function.

So we can take this - what would be blocks of different conditions - and we're putting that away and we're able to access it from within a single value space, if that makes sense?

So what would be happening at a rule level is getting condensed and put into a value level.

I think that's one of the more interesting things we can do here.

Sorry that this girl is so disappointed in you.

I don't know what you did, so let's look at Mixin as well.

It's a very similar syntax.

The Mixin prototype is so much newer that, barely any of the features work, so I cannot show you almost anything.

A Mixin basically just acts like - it just takes what would be nested styles and it just puts them into a place.

Right now if I don't have this extra wrapping ampersand, the browser crashes.

And actually I shouldn't be showing it to you here... this one I need to - so because these are so new, I have to run Chrome from the command line in order to test it.

So let's run Chrome from the command line, and then see if we can avoid crashing Chrome.

Great.

So I made a gallery Mixin, and it just has all the different code that I need to create that Gallery Layout, and even a little media query in there to make the borders bigger, and the gap between things bigger.

Yeah.

There we go.

There's the break point where the gap and the borders get larger, and then our, gallery works, so we're just mixing it in right there.

Everything expands where it is.

Mixins should be able to take parameters similar to functions, but at this point they can't, but they do have access to variables that are outside.

So we can start playing with "what would it be like if I passed in this parameter?" But right now the parameter - if I try to put parameters in, I'll crash the browser.

Great.

So again, these are just getting applied.

It sort of expands where we apply, where we use these '@apply --gallery;', so if we say 'display; block;' after, that will override our display grid from the gallery, but if we say 'display; block;' before, the gallery will override.

So where we apply the Mixin matters.

It sort of just, expands, right there, like a block of nested CSS.

Let's get those two browsers next to each other.

Okay, still disappointed - all that and we still haven't made her happy.

One that I always want is Gradient Text.

Because Gradient Text feels like it should be simple but it turns out we have to do all these things, and then I always wanna wrap it in a supports query, which at this point is a little bit absurd - putting a supports query for something older into a newer feature - there's not gonna be any browser that gets this far, and then is: "I don't support that." But you get the idea, I can put a supports query there, I can check that the feature is supported and then only apply certain things.

And part of what I want with this, is if I'm gonna have Gradient Text, oh, which - right - again, I am showing it to you in a browser that doesn't support it, so let's go over there.

There we've got our Gradient Text, it's just small, but there's all these little things that I want to do with Gradient Text that I don't want to apply unless Gradient Text is supported.

And so I don't want to set, I don't even want to set that background.

'cause if I just set that linear-gradient background and then let's say Gradient Text wasn't supported, the outcome kinda sucks.

And then I have to figure out what I want to do with it.

So instead I want to pass that in as a custom property and then only apply it if everything else is going to apply.

Mixins - very similar syntax to the Functions.

We say at @mixin,, we give it a name, we define some parameters, but this time, it's all output.

So nothing is private inside of a mixin, everything is gonna make it out.

There's no result descriptor that sends one thing back, everything goes back.

There's some discussion of: "do we need, like, an @private, or an @only return this," inside of a mixin so that some things can be private to the mixin.

But that's still under discussion, so go leave some feedback.

We'd really love to see use cases on mixins in particular.

Nothing is private inside of them currently, everything just acts like nested rules.

Styles expand where they're applied and the prototype is very limited.

So eventually we'll be able to do directly nested styles without doing the ampersand wrapper, we'll be able to do parameters, we'll be able to move definitions later.

Oh - right now the browser crashes if you put the mixin after the code that you're - after the 'apply', but that should work fine.

The mixin shouldn't have to be defined first, but you can have fun crashing Chrome.

It's a great time.

I also wanted to show you Inline conditions with the if() function, just because they're very useful in, in writing your own custom functions.

You can tell this demo is working because it's red...

maybe chose the wrong color there.

Alright, so what I have is, an if statement that's just doing media query.

So basically this gives us inline media queries on one property, and it's the same as the media query wrapping it and then defining the property over and over, but we're just putting that all in one place.

So this is gonna get unwieldy if you're doing this for every property, and then you should do it at the block level.

But if you're just doing it one property at a time, this might be a little cleaner.

So what I have is if we're over 40ems, it should be red, over 30ems, orange, and you can see that the first one is applying.

I am over 10ems, 20ems, 30ems, and 40ems.

I have to be over 10ems to be over 40.

As I get smaller, we go down that thread, but you can see the order here matters - it's gonna use the first one that it gets to where the value applies.

And if none of the conditions apply, we go to the fallback else.

there's some weird stuff in here, like we've gotta use the media function to do media queries.

We can do multiple things, so we could say: 'and screen', we can...

that might need to be media screen...

I don't know.

There's definitely some - there's some new stuff in here and, it's constantly changing and I don't know what's supported where, so you're just gonna have to play with some of it.

But you should be able to do combinations of different conditions, within here.

Then you've got this colon and now semicolon inside of a function inside of a property, which is a little bit weird as syntax, but there it is.

And I think we can either have that final colon or leave it off, and that doesn't matter too much.

But that's a really useful thing when we're starting to play with our custom functions.

Guess what?

There's somewhat limited support, but at least we're far enough to know that the support is limited, so we're doing pretty, we're doing pretty good here.

There are currently three conditions, media, supports, and style.

I asked why there's no container, right?

That's the obvious one that we would think of next.

What about a container query?

So what this is doing is it's querying the exact element - it's querying the current style on this element, it's not looking at the parent, and we can't do that with container queries.

So container queries aren't allowed here because container queries are looking for some other element.

and here the 'if' - the 'if' conditional function is looking at the element that we're styling, which is pretty powerful.

So, like variables, it has some sort of looping logic where if you create an infinite loop of, conditions referencing other conditions, it's just gonna not work.

So these are just some of the latest features inspired by outside tools like Sass and whatever - we got variables, pre-processor had all sorts of math and color functions before they became part of CSS, nesting started there.

If() and custom functions and custom mixins are just the latest abstractions to make the jump.

But every time this happens, the CSS version works differently than the Sass version because they're just fundamentally different languages.

Which takes me way off track because we need to talk about variables and I know that PBK wanted me to talk about mixins and functions, but we need to talk about variables.

When we got the spec, it wasn't called variables, it was called "CSS Custom Properties for Cascading Variables Module Level 1." Variables are in there, but they're like a thing that we can do with this more powerful feature, this Custom Properties.

These are properties, they're properties that we define, that cascade and inherit on elements.

That's not how Sass variables work, Sass variables have lexical scope.

So if we define a variable inside of the HTML - that's not available inside of the body - that's available inside those two brackets, it's lexile scoped in the code based on how we wrote the code.

Custom properties don't work like that.

They are scoped in the DOM, so they will be available on that HTML element, they'll be available on the body because they're going to inherit by default.

It's just a fundamental different way of thinking about what we can do with these values because they're gonna move around in a different way.

So that becomes important for various things we might want to do.

So here, this doesn't work.

I set the action color to navy and then I applied the action color to button, and then I changed... I changed the action color in different contexts.

And for years, this was the main question I got about Sass: "Why doesn't that work, why won't, my button in the main or the aside or the footer, change to the different colors? And it's because of that behavior, of lexical scoping versus - this doesn't have any awareness of the DOM.

So in order to make it work in Sass, we

would have to redefine this every time - we'd have to say: "okay, I've changed what action is, now reapply action.

Oh... I guess we want that on the button... so then I'm doing some nesting, and it gets a little bit more complicated, or we can do it in CSS where - actually, let's leave that around... let's just take all of these and change them to action.

This one will have to have the var...

and that's.... not working.

What's that?

Oh, you're right - action should be on root.

I can't even do it globally there.

Now, some people use root.

I use HTML.

Root - people will tell you that root applies to the root of other things too.

But unless you're using other things, that seems like a danger to me.

And if you put root styles in an SVG and you only have that SVG, the styles will apply to the SVG, but they apply to the root-est root.

So you put that SVG into HTML, and now you're styling - from your SVG code, you're styling the root of your HTML document.

I feel like you want to be a little bit more specific about what you're doing.

I use HTML.

That was... off topic.

Okay, so we get some different behavior here from variables and really variables expose to us - and I'm not gonna have time to go into all of these demos - but they expose to us the entire value resolution process in CSS, like we have to start learning all this complexity that has been hidden behind the scenes if we really want to understand what variables can do.

So every value in CSS, in order to be applied, has to go through these steps.

First filtering, then cascading, then defaulting, which includes inheritance, and then resolving, and then formatting and constraining - but I'm not even gonna touch on those today - they sort of happen at the end.

But the goal is: for the browser to render a page, it has to go through every single element in the DOM, so every HTML element, one at a time.

And for every property on that element - and there's now... 560?

I don't remem.. 650?

I don't... I'm dyslexic.

There's a lot of properties and the browser has to go through every single property on that element specifically and find exactly one value.

And I am going to assure you that you have never specified all of them, and that sometimes you have specified multiple things - you've told the button to be three different colors and the browser has to determine which, and then on another element, you haven't specified any color and the browser still needs a color, 'cause it's really hard to render anything without knowing what color to render it.

So starting at the top of this - the filtering step - every piece of this maybe feels obvious.

In order for a declaration to be - that's the property and the value together - color: blue... in order for it to be relevant, it has to be in a style sheet that applies to the document.

Yep.

It has to be not in a false condition.

So if it's in a media query, that media query has to be true.

It has to be in a selector that matches the element - that seems fair - we're not going to apply it if it doesn't match, and it has to be syntactically valid, and that's where things get complicated.

So... okay: color: teal - we're putting that on the HTML...

that seems valid, right?

Teal is a color, it's one of the named colors, right?

If we said: color: 3ems.

That's not valid.

You're not allowed to do that.

So the browser knows and it can discard right at the beginning - we don't do any cascading with this value, we don't do any inheritance with this value, it's just thrown away right at the top.

Great - that's because the browser knows that color is a typed syntax.

It accepts color values and if we pass in something other than a color value, it can get rid of it.

So: 'color: teal', great!

'color:3ems' - no, 3ems is the length.

What about this: 'color: var(--my property)?

That seems valid.

We don't know what's in there though...

--my-property: teal; - also valid.

So that's good, but so is : '--my property: 3em;' - this is valid.

So what happens if we put those together?

That's still valid: '--my-property: 3em; color: var(--my-property);' - all good.

So that makes it through.

That's gonna cascade and it's gonna fail later.

And the same is true even if we define the syntax for our custom property, because the browsers are doing this as they're parsing the code, and they're highly optimized to do this very quickly, and they don't know if you're gonna override that '--my-property' description later.

If you're gonna register it three times in different ways, they don't know which one is gonna be the real one - they haven't done all that figurin' out yet.

So they can't, at this point, trust, what we've registered.

So this is all valid, even though we've explicitly said it shouldn't be.

So, declarations containing the 'var' function cannot be evaluated at parse time... and I'm sorry that you have to think about these internals of the browser, but this makes a big difference for how our code works.

And now it makes more of a difference because now, declarations containing the 'variable' function, the 'if' function, or any 'custom' function - all of these can't be evaluated at parse time, so more of our code is getting through, and it's going to have to cascade even when it might become invalid further down the road - invalid at computed value time, which is just a phrase you shouldn't have to learn.

People will call it 'IACVT' if they're real nerds - this is what they're talking about.

So let me show you what that means for us.

I've got some styles here.

My color is 'cold coffee', so when I set the color property to 'cold coffee', that's just ignored because we know that cold coffee is not a valid color - it should be - I should be a valid color.

What?

But, that's discarded, it's ignored, and so we still get our 'medium violet red', which is the best color - 'deep pink' is pretty good.. 'teal' is really good.

What?

'Hot pink'?

'Hot pink' is good.

'Deep pink' goes harder.

If we have the same 'cold coffee' or 'Miriam' in a variable and we apply that variable to color, suddenly we're falling back to the inherited value from HTML, because this cannot be discarded at parse time because this is valid syntax.

And so it doesn't become invalid until computed value time when we've already finished the cascade and we've thrown away all of our other values here.

So the result that we get is the explicit unset.

We're just unsetting that value and then we're getting the default instead.

So that's risky.

So if we're using things inside of a variable that might not be supported, we're gonna want to put that inside of an @supports or something.

And it's because of this invalid computed value time phrase that you shouldn't have to learn.

So once the filtering is done, then we can move on to cascading, and this is gonna resolve conflicts when we've defined too many colors.

So that's all the cascade is for - it's for narrowing us down to, one or zero values.

If we've defined anything, we'll end up with one.

If we haven't defined anything, we'll end up with zero.

But if we have more than one, we have to get down to one - filter out the extras.

Like other properties, variables cascade.

So they go through this whole process.

So, 'importance' - that's part of the cascade.

What does this step do?

Does anybody have thoughts on what... if we said... oh, I don't have enough there to give you the context for the question, so let's just jump into the demo.

So what is 'Important'?

So if we're doing this in Sass, we've got 'maroon !important;' and we put that on yeah... .sass and .var... both of those classes apply to the same thing.

So we could just say 'main', but those are both applying.

Alright, so if we set the background to our .sass 'important', we get importance in our output.

And that's why that background is winning.

But when we set importance on the custom property, that's not passed along to the background, that means that if I try to override importance, here: importance - you wanted 'hot pink'.

Here we go...

the important value wins.

The important custom property wins, that importance is on the custom property - it's not passed along to the property where we're using that color.

So that's again going to confuse us.

Functions on the other hand, don't cascade.

They're defined outside of the cascade because they're not defined on an element, and therefore variables inside of the function don't cascade either.

Importance is just not allowed - if you put importance in a function, it just fails.

It's just doesn't work at all.

There's no cascade there to deal with.

Mixins do contribute styles to the cascade.

They're just nesting, but not nested, just pulled out.

Okay, we got some movement over here.

This seems like one we're gonna want in the other browser, where we have ... oh, so I created....

just a bunch of variables, and I just tucked them away into a function called 'easing'.

so I've got just all these different cubic bezier curves that do different sort of easings, and so one way that I could use those is I could apply that, that mixin, and now I have access to all of those variables, and then I could apply one of those variables to the second one there.

And now we can see the second one is doing the easing that I asked for, bouncing all around.

That's great.

That's one way that we could do this - have all of these variables, put all the variables into the cascade, now we have access to them.

The other way we could approach this is let's not use that mixin, let's use a function.

And this function is just doing a big old 'if' statement.

There's no easing variables in my cascade now.

If I inspected the document, I wouldn't find variables for these easing functions anywhere, but I have a function where I can pass in the name of one, and if that parameter is set to that name, we're gonna get that curve.

So I can just say: 'easing'(in-out-back);' and we have parameterized variables.

We can think of this 'easing' is the variable, but I can pass in a parameter and say, 'which one do I want?' So those are two approaches that we could take to using this for getting access to our design tokens, our font sizes, our colors, whatever it is that we want.

There's a whole lot more to the cascade that we're not gonna go into at all.

We still have to deal with 'defaulting'.

Defaulting fills in missing values.

So this is after we've cascaded.

So we start with all these boxes, and they're not actually laid out, but they're going to be, we've got all these boxes and they've got no style on them, and then the cascade comes in and says: "Select these six boxes and give them style".

So those get style and then we have to go in and default, and that includes inheritance and we're just filling in the values everywhere else.

So the default process sometimes includes inheritance, sometimes doesn't, it depends on the property what it's gonna do.

So some properties inherit and custom properties inherit by default.

But inheritance requires a lineage.

So if we have HTML color: red, body color: blue, body: main', it's going to be blue.

It's gonna get it from its direct parent.

if we do this, we take out the direct parent, we get the red from the HTML, but we need that direct lineage - it needs to stay consistent because actually we're always getting it from the direct parent and that parent just happens to be getting it from its parent.

So we never.. like, body> main is never going to inherit red from HTML exactly.

It's gonna inherit it from the body, which inherited it from, HTML.

We need that direct lineage, to build generational wealth or whatever.

Take my course and then you too can be an oligarch.

Sorry.

Sometimes I get off track.

Custom properties allow us to carry hidden context around inheriting across generations so we can, I'm gonna stop jumping into demos 'cause I'm low on time, but, we can pull a value out, have it inherit in the custom property instead of in the main property, so it's not inheriting as a color that we're gonna see.

It's inheriting as a 'my color' that you're not gonna see until I plug it back in, and we can skip generations with that, but most properties don't inherit.

And there's a, sort of simple logic that you can think through for when a property is going to inherit or not.

If I added a span in the middle of this text, I would want to not see it.

A span is an unstyled inline element.

If I add one, nothing should happen until I style it.

So what properties would I need to apply to that span in order for nothing to happen?

And what styles would I need to not apply in order for nothing to happen?

So if I applied all of the borders and the margins and stuff from the parent, that would do layout shifting stuff.

If I didn't apply the colors from the parent and the font from the parent, I would also get some shifting because I would be getting the initial values of those or something so we can logic through it that way.

But generally, unstyled, inline boxes like spans should just blend in.

And so generally text styles inherit and box styles don't inherit.

And that's just a general rule that's not always true, but almost always.

So if there's no inheritance on a property, then we need the initial value.

Each property gets its initial value from the definition table.

You can go look it up in the spec, but MDN will also tell you, what the initial value of display is in line.

Doesn't matter what element we're looking at - the body element, section element, spans - all inline.

They're all inline until the browser comes along and provides something else.

Initial values work like that.

The initial value - here's another of these.

The initial value of a custom property is the 'guaranteed invalid' value.

That's what it's called.

You have no other way to get it, except by not defining a custom property, and then calling that custom property and 'guaranteed invalid' is useful, and you've used it, whether you've thought much about it or not.

Guaranteed invalid is what we get - need to go here, guaranteed invalid is what lets us get the fallback.

If we have any value.

Okay - so not a valid color - that's being ignored.

All the browsers are ignoring these ones where the syntax is obviously wrong.

We saw that earlier, but this is good syntax.

Invalid value.. we're getting.. now, wait.

Invalid value.

What happens if we define it?

Oh, we're not seeing that because we're seeing this.

Alright, so if we define it - our custom property - we say it should be a color and the initial color is blue.

When we get an invalid color, this is not a color, we're just gonna get blue, we're not gonna get the fallback.

But if we don't have it defined now... Oh, because there's a value there, it's not guaranteed invalid, and we don't get the fallback.

And that can cause a problem for us.

We only get the fallback - that 'rebeccopurple' if it is actually the guaranteed invalid value.

And we can get there by saying: 'I want invalid' - let's say we had that, let's just - 'I want the invalid value to instead be the initial value', and there we get the fallback.

But if we define it, we won't.

Because the initial value now is blue and we'll get blue, and we will never access this fallback unless we have the guaranteed invalid value.

And this is why we need to talk about variables because we've gotta deal with invalid at computed value time.

We've gotta deal with, guaranteed invalid values, the whole works.

But they're a little bit different with functions andI'm low on time so, there's more here that you can look at.

Typed properties will never fallback.

Functions can also be typed.

We can we can tell the browser what type to expect, and here if we're wrong, if we try to return the wrong value, we will get guaranteed invalid, so then we can start to use functions with variables to like access fallbacks, but the functions don't have a fallback syntax, so then we have to put the function inside of a variable and then the...

Anyway, we can use variables to provide fallbacks for our functions.

And then we can also use these keywords.

Again, like importance, these keywords apply to variables.

If we say that a variable is set to initial, that doesn't pass along when we use that variable in the color doesn't get reset to initial - the variable gets reset to initial because they're just properties that inherit and cascade.

But, so they apply to the custom property, and you can dig into those demos later if you wanna see that happening.

But functions should pass through those keywords.

I think they don't yet in the demo, but, so I don't have a demo of that, but it should pass through.

One of the big differences here that we can get with functions versus variables, is that when I define a variable, say on the HTML element...

I'm not doing that.

Okay.

So here's a function where I've used initial on all of my definitions for the parameters, and I'm using that to determine how many parameters were passed in.

So if I pass in fewer parameters.

it changes the values of the, it changes where the, breaks are for the gradient - the color stops, and it's just checking: 'Do we have a defined value in the first slot?' Then we have one color.

'Do we have a defined value in the second slot?' Then we have two colors, and it just goes down through, to make - to count up how many colors we have, and make sure it works, and then we can, define all sorts of functions that get very complex and, oh, I have Amsterdam here.

I spent time on it, so I should show it to you.

Amsterdam.

Yes, God, pray.

Thank you.

There's more, more demos here that you can look into, that show off different things about, how this value resolution affects functions and mixins, how all of this inheritance stuff works.

Because we more and more have to deal with all these intricacies of the browser, in order to render the page.

So yeah, that defaulting happens, after the... The resolution happens after defaulting, but it happens down through the tree, so we actually inherit resolved values and then types matter.

And I could show you all sorts of things about how like red and F00 are not the same until we tell the browser that they're colors and then they become the same.

So then if you're doing, 'if' comparisons, you need to make sure that your types match, which all gets very complicated.

What else, can I show just very quickly?

That's custom units.

There's all sorts of fluid scales for you, John.

Conditional Border Radius.

I like this.

This was requested by Adam.

So it's just a function that finds out - are you within, let's say - within 3ems.

It will switch.

there's lots of little useful things that we can do and they're all things that we could do before, but, they just had to live in our code and be repeated in our code and now we can pull them out.

I played with, generating entire color palettes from a function, having access to them.

Adam gave me this physics demo and I pulled it into a function.

And then, this is Mia - GPT.

You have to define the color.

You have to describe it because we're into conversational interfaces now, so you have to say 'faded lilac still, springlike', and then you pass that prompt into Mia GPT and it just goes and it just does an 'if' to see if we have that description.

Then it gives you the color that matches.

It's very useful.

If you get this even a little bit wrong or change it to single quotes, it will all fail.

I think it's a perfect example of ai.

You might notice I'm low on mixins.

They're a little bit not as far along, and also we need a little bit more feedback on whether they actually provide anything different in CSS than they do already in pre-processor.

If we can already do it in pre-processors, do we need it here?

So, go leave feedback.

Some questions that I always get: "Are there performance issues?" You can probably find 'em, but browsers are just optimizing all the time, and if you report a bug, they're gonna fix the bug and then your data is out of date.

We need to stop trusting 10-year-old data about what's performant in CSS - it's not a thing - generally in CSS that's not an issue.

To cause issue, try to restyle as many elements at once as possible.

So select everything and change the styles.

That's gonna be your biggest performance hits.

Is Sass dead yet?

Maybe for you.

If you don't need it, great - then it's dead for you.

If you're building design systems where you need things like loops, lists and objects, then it's maybe not dead for you.

Why give the browser problems that we can solve on the Server?

So it's really just a trade off.

It's like it's dead if you want it dead, and it's not dead if it's useful.

So mixins and functions, they're not really changing what we can do.

There's not really new functionality here at the core, it's all calculations that we can already do.

But we can just abstract them away.

If you thought CSS was programming before then it's still programming, but with more features.

And if you did not think it was programming, it's still not programming, but with more features, it's up to you.

But we are trying to improve the syntax and maintainability, get more of this meaning.

And if you just wanted to write some good styles, well great.

I've got a course coming up and hopefully I can help you with that if you need.

Thank you.

Thank you.

Miriam.

We can, have a seat in the hot seat.

Do you like the flowers?

I love the flowers.

Thank you.

They're lovely flowers.

There's no deep pink.

I think it's just a light pink.

Are they just for me?

Sure.

Yeah.

Yeah.

We actually have 5.7 million questions for you.

Great.

And we have time for one.

Great.

So I don't think you'll have a, I don't think you'll have a calm lunch break 'cause everyone's gonna come up to you asking the questions.

Sorry about that.

Also the QR link is broken someone said... that's not my fault.

So whoever's fault it is, fix it, please.

I don't know who, but Manuel asked, can we have some typing?

So now the feature requests are already coming.

You don't even have it yet.

And the feature requests are coming.

Yeah, there is typing.

You can type both - I mean you can type custom properties using the @property, and then you can type, functions and function returns.

You can say what kind of type it should return.

And you can combine multiple types and you can say what kind of type should be accepted in each parameter.

So you can type to your heart's content.

I think they meant something like a JS doc-ish.. CSS doc, kind of thing.

I can't fix everything for you.

Oh, okay.

Fair.

We'll do one more.

When defining a function - this is a question from Rohire - is there a reason to use the same dash-dash prefix, as with custom properties, it can be difficult to tell if it's a function or a custom property.

Yeah, the dash-dash prefix - custom properties are just the most popular place that it's used, but they're actually used all throughout CSS recently, for a lot of things, functions aren't just the second thing to use them.

But it's basically.

Do you remember - who is old?

- Do you remember when we had dash-webkit-dash-dash?

I guess we still do, Adam had it in his talk.

When we just take out the browser name, then, it's just two dashes and that's us.

We don't have a name.

We're just those two dashes.

And it's basically used anywhere that we are going to be in the same namespace that CSS already has things, and we don't want conflict.

So then we just, try to reuse that naming convention so that, every time you see those two dashes, you know that it's your problem.

Alright, one more.

Oh, okay.

Now I have to scroll back through.. oh, no.

5.7 million.

Oh, what are we even doing here?

Richard asked why function results, and not function return.

Yeah, it's because, we thought people are too used to return being able to return early.

And that's not what happens when you hit the first return, you don't go back, you don't go out.

Instead you're getting a declarative result.

And that result can change as we continue to go through the function.

And so we wanted something that didn't feel like you're going to escape out of this function as soon as you hit it, but something that's like we can build on the result and then send it back.

Do you think there's a limit to how many features we'll end up adding to CSS?

Yes.

What's the limit?

It's a declarative language.

I don't think we're going to add loops, I don't think because of that same thing, right?

Because it's just a bucket of properties.

There's no stepping through and like changing the value of a property and then going back or changing it... like we don't have that sort of control flow.

We have a bucket of properties, and one of them gets returned, and so that's sort of the limit.

And that's why if you do need things like loops and lists that you can loop over, you might want to keep using Sass.

But if you don't need those things...

So I think yeah, we're, bumping

up against that limit right now.

Okay.

I wanna thank you, for everyone here, including me, for everything you do for the Web.

Oh, thanks.

And for giving your talk today.

Thank you very much, Miriam.

Thank you.

Is Sass Dead Yet?

CSS Mixins & Functions &c.

Miriam Suzanne

Photo of Miriam Suzanne speaking into a microphone.

Things were also… Way More Complicated

Meaningful CSS for a Humane Web

by Miriam

THE MEANING PROBLEM:

  • Repetition is hard to maintain.
  • Relationships are hidden.
  • Reasons are unclear.

				.meaning {
					padding: 1.5em;
					background: #d33682;
					color: #080205; }
					.meaning > h3 {
						margin: -1.5em;
						margin-bottom: 1.5em;
						padding: 1.5em;
						background: rgba(8, 2, 5, 0.5);
						color: #d33682; }
				
Illustration of a paper-like card containing the text "CSS is awesome" and "But it lacks meaningful abstractions."
The control which designers know in the print medium, and often desire in the web medium, is simply a function of the limitation of the printed page... We must “accept the ebb and flow of things.”

—John Allsopp, April 2000
http://www.alistapart.com/articles/dao/

/* originally +grid-col(3) */
				article {
					@include span(3);
				}

A simple language Can Cause Complex Code


				article { grid-column: span 3; }
				

Don’t Use My Grid System

Screenshot of an article titled "Don’t Use My Grid System" by Miriam Suzanne, with a QR code visible on the right.

Complexity
Is a Moving Target

Abstractions

Make it Better & Worse

I'm back
With New Abstractions

CSS Functions and Mixins Module

W3C First Public Working Draft, 15 May 2025

https://www.w3.org/TR/2025/WD-css-mixins-1-20250515/

CSS @Function Rule

limited support
Icons for Chrome, Edge, Firefox, and Safari browsers are shown with question marks, indicating uncertain support status for the CSS @function rule.

CSS @Mixin Rule

  • unknown support

Need Feedback

github.com/w3c/csswg-drafts/issues https://github.com/w3c/csswg-drafts/issues

Issues · w3c/csswg-drafts

Screenshot of the GitHub issues page for the w3c/csswg-drafts repository, showing a list of open issues in the CSS Working Group’s spec tracking repository.

Need Feedback

github.com/w3c/csswg-drafts/issues

				@function --double(--first) {
					result: calc(var(--first) * 2);
				}
				div {
					--gap: 1em;
					margin: var(--gap);
					padding: --double(var(--gap));
				}
				
VS Code editor showing a CSS code example demonstrating a custom function and CSS variables, alongside a visual representation of two boxes labeled "var(--gap)" and "--double(var(--gap))".

				@function --times-two(--value) {
					result: calc(var(--value) * 2);
				}
				div {
					--gap: 1em;
					margin: var(--gap);
					padding: --times-two(1);
				}
				

				var(--gap)
				--double(var(--gap))
				
VS Code editor showing a CSS function definition for doubling a value, with a div example using CSS custom properties and the function; on the right, two boxes display the result of using var(--gap) and --double(var(--gap)).

				@function --times-two(--value) {
					result: calc(var(--value) * 2);
				}
				div {
					--gap: 1em;
					margin: var(--gap);
					padding: --times-two(12px);
				}
				
Screenshot of a code editor displaying CSS code with preview boxes showing the result of var(--gap) and --double(var(--gap)) function usage.

				@function --times-two(--value) {
					result: calc(var(--value) * 2);
				}
				div {
					--gap: 1em;
					margin: var(--gap);
					padding: --times-two(2px);
				}
				
VS Code editor showing a CSS function and a CSS rule for a div element, alongside two rendered boxes displaying "var(--gap)" and "--double(var(--gap))".

				@function --times-two(--value) {
					result: calc(var(--value) * 2);
				}
				div {
					--gap: 1em;
					margin: var(--gap);
					padding: --times-two(2px);
				}
				
  • var(--gap)
  • --double(var(--gap))
VS Code editor showing a CSS code example with a custom function and variable usage, alongside two code-like output boxes on the right illustrating how the variables/functions are used.

				@function --times-two(--value, --second) {
					result: calc(var(--value) * var);
				}
				div {
					--gap: 1em;
					margin: var(--gap);
					padding: --times-two(2px);
				}
				
var(--gap)
--double(var(--gap))
VS Code-like code editor showing a CSS custom function for multiplying values, alongside a preview of CSS variable output.

				@function --times-two(--value, --mult: 3) {
					result: calc(var(--value) * var(--mult));
				}
				div {
					--gap: 1em;
					margin: var(--gap);
					padding: --times-two(2px, 8);
				}
				
VS Code editor showing a CSS function definition and usage on the left, and a web preview on the right with two styled boxes containing "var(--gap)" and "--double(var(--gap))".

				@function --times-two(--value, --mult: 3) {
					result: calc(var(--value) * var(--mult));
				}
				div {
					--gap: 1em;
					margin: var(--gap);
					padding: --times-two(2px, 8);
					margin: var(--value);
				}
				
VS Code editor showing a CSS function definition using custom properties and a preview area with two output boxes demonstrating the use of variables and a custom function.

				@function --times-two(--value: initial, --mult: 3) {
					--fallback: var(--value, var(--gap));
					result: calc(var(--fall) * var(--mult));
				}
				div {
					--gap: 1em;
					margin: var(--gap);
					padding: --times-two();
				}
				
var(--gap)
--double(var(--gap))
Code editor window showing a CSS function for multiplying a value, and a preview window displaying the result of CSS variables in use.

				@function --times-two(--value: initial, --mult: 3) {
					--fallback: var(--value, var(--gap));
					--fallback: calc
					result: calc(var(--fallback) * var(--mult));
				}
				div {
					--gap: 2em;
					margin: var(--gap);
					padding: --times-two();
				}
				
Code editor screenshot showing CSS with a custom function and a div using CSS variables. To the right, a browser preview displays two outlined boxes: one labeled "var(--gap)" and another labeled "--double(var(--gap))".

				@function --times-two(--value: initial, --mult: 3) {
					--fallback: var(--value, var(--gap));
					--fallback: 1em;
					result: calc(var(--fallback) * var(--mult));
				}
				div {
					--gap: 2em;
					margin: var(--gap);
					padding: --times-two();
				}
				
Screenshot of a code editor showing a CSS custom property function on the left, and two blue boxes on the right. The top box contains the label "var(--gap)", and the bottom box contains the label "--double(var(--gap))".

				@function --times-two(--value: initial, --mult: 3) {
					result: calc(var(--fallback) * var(--mult));
					--fallback: 1em;
					--fallback: var(--value, var(--gap));
				}
				div {
					--gap: 2em;
					margin: var(--gap);
					padding: --times-two();
				}
				
Split-screen slide: On the left, a code editor displays a CSS function and a CSS rule for a div. On the right, there are two blue rectangles; the top box shows the label "var(--gap)", and the bottom box shows the label "--double(var(--gap))", both in a white box centered within each rectangle.

				@function --negative(--n) {
					result: calc(var(--n) * -1);
				}
				div {
					--amount: 6deg;
					rotate: var(--amount);
				}
				div:last-child {
					rotate: --negative(var(--amount));
				}
				@layer reset {
					* { box-sizing: border-box; }
					html { block-size: 100%; }
					body {
						margin: 0;
						min-block-size: 100%;
						display: grid;
						place-content: center;
					}
				}
				
VS Code style code editor showing a CSS code example for creating a custom negative function, with a live preview on the right displaying two magenta squares, one rotated positively and the other rotated negatively.

				@function --my-function(--optional, --parameters) {
					result: 4; /* returned value */
					--any-logic: using custom property syntax;
					--declarative: like any other CSS context;
				}
				

CSS Functions Act Like Values


				button {
					padding: --space();
					background: --theme-color(brand);
					border: var(--border-width);
				}
				

A declarative Bucket of Unordered Properties

Parameters & Internal Variables are Private


				@function --user-scheme() {
					result: light;
					@media (prefers-color-scheme: dark) {
						result: dark;
					}
				}
				html {
					color-scheme: --user-scheme();
				}
				@function --light-dark(--light, --dark) {
					result: if(
					style(--scheme: dark): var(--dark);
					else: var(--light);
					);
				}
				
Split-screen screenshot showing a code editor (with CSS code defining color scheme logic using custom functions and media queries) on the left, and browser developer tools displaying HTML and CSS on the right.
Photograph of a young child with an unhappy and disappointed facial expression, looking directly at the camera.

Gallery Mixin


				@mixin --gallery {
					& {
						--gap: 0.5em;
						display: grid;
						grid-template-columns: repeat(
						auto-fit,
						minmax(min(var(--gallery-item, 12em), 100%))
						);
						gap: var(--gap);
						padding: var(--gap);
						@media (width > 30em) {
							--gap: 1em;
							--item-border: medium;
						}
					}
					img {
						border: var(--item-border, thin) solid;
					}
				}
				
Screenshot of a code editor window showing a CSS mixin for a gallery layout, alongside a terminal window running a Chrome browser command. Faded in the background is a stack of repeated child portrait photos, illustrating a gallery image grid.

				@media (width > 30em) {
					--gap: 1em;
					--item-border: medium;
				}
				img {
					border: var(--item-border, thin) solid;
				}
				figure {
					display: block;
					@apply --gallery;
					--gallery-item: 18em;
				}
				
Screenshot of a code editor showing a CSS code example for a gallery mixin on the left, and a UI preview with repeated images of a frowning child arranged in a 2-column, 4-row grid on the right.
Photograph of a young child with a frowning, displeased facial expression, shown within a screenshot of a code editor or web preview tool.

Gradient Text

Screenshot of a code playground/editor displaying a file titled "Gradient Text".

				@mixin --my-mixin(--optional, --parameters) {
					/* all styles are output? */
					--this-is: public;
					padding: 1em 2em;
				}
				

CSS Mixins
Act Like Nested Rules


				aside {
					@apply --callout-box(info);
					position: relative;
				}
				
Where style rules are allowed

Very Limited Prototype

These things will eventually work…

  • Directly nested styles
  • Parameters
  • Moving definitions later

Very Limited Prototype

These things will eventually work...

  • Directly nested styles
  • Parameters
  • Moving definitions later

Inline conditions
With the if() Function

If() with Media Queries

Screenshot of a CodePen "Result" pane displaying a solid red area, demonstrating a CSS result—likely conditional color output using the if() function with media queries.

Just the latest
Inspired By Sass et al

  • Variables
  • Math and color functions
  • Nesting
  • if()
  • @function
  • @mixin

CSS versions
Work Differently

We need to talk about --Variables

Photo of a woman's eyes is partially visible behind the heading text on the slide.

Cascade & inherit
As Properties of Elements

Sass variables use Lexical Scope


				html { $color: green; }
				body { /* $color == undefined */ }
				

Custom properties use DOM Scope


				html { --color: green; }
				body { /* --color == green */ }
				
$action: navy;
				button { background: $action; }
		
				main { $action: maroon; }
				aside { $action: teal; }
				footer { $action: rebeccaPurple; }
				
Split-screen slide showing on the left a code editor with SCSS code defining a variable and button/background color assignments, and on the right a diagram with four labeled buttons: "default button", "main button", "aside button", and "footer button", each contained in a dotted outline representing different sections.
button {
					$action: navy;
					background: $action;
				}
		
				main {
					$action: maroon;
					background: $action;
				}
				aside { $action: teal; }
				footer { $action: rebeccaPurple; }
				
Split-screen slide: On the left, an SCSS code editor shows variable assignments for button, main, aside, and footer backgrounds. On the right, a diagram illustrates nested boxes with dotted borders containing four buttons labeled "default button", "main button", "aside button", and "footer button" to represent the structure and visual effect of the code.

Value Resolution

  1. Filtering
  2. Cascading
  3. Defaulting (includes inheritance)
  4. Resolving
  5. Formatting
  6. Constraining
variables expose the process
  • On each html element
  • For every css property
  • We need exactly one value
Same for custom properties!

Filtering
Finds Relevant Declarations

  1. In a stylesheet that applies (see media attr)
  2. Not in a false conditional rule (see at-rules)
  3. In a selector that matches the HTML Element
  4. Is syntactically valid

Result is a (maybe empty) list of ‘declared’ values

✅ Valid Syntax


				html { color: teal; }
				

Not Valid Syntax


				html { color: 3em; }
				

				html {
					/* color: <color>; */
					color: teal; /* applied! */
					color: 3em; /* discarded */
				}
				

				html {
					/* color: <color>; */
					color: teal; /* applied! */
					color: 3em; /* discarded */
				}
				

✅ Valid Syntax


				html { color: var(--my-property); }
				

✅ Valid Syntax


				html { --my-property: teal; }
				

Still Valid Syntax


				html {
					--my-property: 3em;
					color: var(--my-property);
				}
				

✅ Still Valid Syntax


				html {
					--my-property: 3em;
					color: var(--my-property);
				}
				@property --my-property {
					syntax: "<color>";
					initial-value: teal;
					inherits: true;
				}
				
Registration applies at computed value time

Declarations containing the var() function

Are not evaluated at parse time

Declarations containing

The var() function
or if() function
or any --custom() function

Are not evaluated at parse time

Invalid At Computed Value Time

“IACVT”

The final value of a var() can't be validated at parse-time

(we need to cascade the variable first), so properties using var() can become invalid at computed value time.


				html { color: orange; }
				p {
					color: mediumVioletRed;
					--my-color: cold coffee;
				}
				/* parse-time validation */
				color: cold coffee;
				/* computed-value-time validation */
				color: var(--my-color);
				/* explicit default */
				color: unset;
				@property --my-color
				
Split slide: Left side displays a code editor with CSS demonstrating parse-time and computed value-time validation. Right side shows explanatory text about var() validation in CSS.

				html { color: orange; }
				p {
					color: mediumVioletRed;
					--my-color: cold coffee;
					/* parse-time validation */
					color: Miriam;
					/* computed-value-time validation */
					color: var(--my-color);
					/* explicit default */
					/* color: unset; */
					/* @property --my-color {
						syntax: '<color>';
						inherits: true;
						initial-value: red;
						} */
					}
				

The final value of a var() can't be validated at parse-time (we need to cascade the variable first!), so properties using var() can become invalid at computed value time.

Value Resolution

  1. Filtering
  2. Cascading (resolving conflicts)
  3. Defaulting
  4. Resolving
Result is zero or one “cascaded” value

Cascade...

Resolves Conflicting Values

When we have multiple declarations

Like other properties... Variables Cascade


				html {
					--color: white !important;
					--color: black;
				}
				

Importance is part of the cascade

What's Important?

<main class="sass var">
					<h3>What's Important?</h3>
				</main>
				

				$important: maroon !important;
				html {
					--important: teal !important;
				}
				.sass {
					background: $important;
					border-color: black !important;
				}
				.var {
				
Code editor screenshot showing HTML and CSS (including Sass) for demonstrating the use of !important and custom properties in styling, next to a browser preview with a border and the heading "What's Important?".

Variables inside Functions Don’t Cascade

Mixins...
Contribute Cascading Styles

Cascade Still Includes

  1. Origins
  2. Context (Shadow vs Light DOM)
  3. Element Attachment (Inline vs Selectors)
  4. Layers
  5. Specificity
  6. Scope
  7. Order of Appearance

Defaulting…
Fills in Missing Values

Diagram of a web page wireframe using dotted lines to represent layout sections such as header, navigation, main content, sidebar, and footer. No specific text or labels are shown.

Defaulting process Depends on the Property


				html { color: red; }
				body { color: blue; }
				body > main { /* inherits...? */ }
				

Each element from its direct parent


				html { color: red; }
				body { color: blue; }
				body > main { /* inherits... ? */ }
				

Each element from its direct parent


				html { color: red; }
				/* body inherits red from html */
				/* body > main inherits red from body */
				

How to Build Generational Wealth For Your Family

Photo of a smiling multi-generational family as background. There is a QR code in the lower right corner.

Custom properties
Can Carry Hidden Context

Hello World

Screenshot of a CodePen result view displaying a centered "Hello World" message inside three nested boxes with dashed borders of different colors, illustrating element nesting and possibly CSS inheritance.
“If I added a span in the middle of this paragraph, what styles would need to apply automatically so the span doesn’t stand out?”

Generally...

Un-styled Inline Boxes (spans) Should Blend In

And so... Text Styles Inherit

“Each property has an initial value, defined in the property's definition table.”

– Cascade & Inheritance, § 7.1. Initial Values

Default initial value...

"The Guaranteed Invalid Value"

like undefined in JS

Browsers totally ignore CSS property/value declarations that they don't understand – falling back to earlier declarations. But sometimes variables look valid at parse-time and are later discovered to be invalid at computed value time.

  • Older browsers (without CSS Variable support) will always ignore declarations that contain variables.
  • Modern browsers (with CSS Variable support) will ignore if the syntax is clearly invalid at parse-time. Otherwise:
    1. [Undefined] If the value is missing (or set to initial), it will try the provided fallback
    2. [Type Error] If the final value doesn’t match the property, it becomes unset – falling back to the inherited or default value.

Browsers totally ignore CSS property/value declarations that they don't understand – falling back to earlier declarations. But sometimes variables look valid at parse-time, and are later discovered to be invalid at computed value time.

  • Older browsers (without CSS Variable support) will always ignore declarations that contain variables.
  • Modern browsers (with CSS Variable support) will ignore if the syntax is clearly invalid at parse-time. Otherwise:
    1. [Undefined] If the value is missing (or set to initial), it will try the provided fallback.
    2. [Type Error] If the final value doesn’t match the property, it becomes unset – falling back to the inherited or default value.

				/* Invalid Syntax is ignored */
				/* all browsers: red */
				color: not a valid color;
				color: var(not a valid variable name);
				/* Wrong "Type" Values = unset */
				/* modern browsers: black (unset) */
				--invalid-value: not a valid color value;
				color: var(--invalid-value, rebeccapurple);
				/* Initial Values = fallback */
				/* modern browsers: rebeccapurple (fallback) */
				--undefined-variable: initial;
				color: var(--undefined-variable, rebeccapurple);
				/* @property --invalid-value {
					syntax: '<color>';
					initial-value: blue;
					inherits: true;
					} */
				
Split-screen slide: Live code editor (left) shows CSS code examples related to variable fallback and value errors; right side contains explanatory text with a bulleted list and numbered points on how browsers handle invalid CSS variable values.

Browsers totally ignore CSS property/value declarations that they don't understand – falling back to earlier declarations. But sometimes variables look valid at parse-time, and are later discovered to be invalid at computed value time.

  • Older browsers (without CSS Variable support) will always ignore declarations that contain variables.
  • Modern browsers (with CSS Variable support) will ignore if the syntax is clearly invalid at parse-time. Otherwise:
    1. [Undefined] If the value is missing (or set to initial), it will try the provided fallback.
    2. [Type Error] If the final value doesn't match the property, it becomes unset — falling back to the inherited or default value.
VS Code editor showing CSS code examples demonstrating invalid syntax, wrong type values, and use of CSS custom properties with fallbacks.

Function Types


				@function --color() returns <color> {/* ... */}
				

Function Types


				@function --color() returns <color> {/* … */}
				

Function Types


				@function --color() returns <color> {/* ... */}
				

Use variables
To Provide Fallback

Guaranteed Invalid Functions (presently: debug)

Screenshot of a code editor interface with the title "Guaranteed Invalid Functions (presently: debug)" but no visible code shown.

explicit defaulting…
"Global Keywords"

  • initial (from spec)
  • inherit (from parent element)
  • unset (inherit or initial)
  • revert (from browser)
  • revert-layer (previous layer)

explicit defaulting…
Global Keywords

  • initial (from spec)
  • inherit (from parent element)
  • unset (inherit or initial)
  • revert (from browser)
  • revert-layer (previous layer)

Functions should
Pass Through Keywords

(not implemented yet)

Screenshot of a code editor or code playground displaying a demo with three vertical color bands, likely used to demonstrate CSS or web layout behavior. The interface tabs show "HTML" and "CSS", and there is a visible label "Flag functions (present?, debug?)" below the preview area.

				html {
					/* --trans: powderblue, pink, white, pink, powderblue; */
					background: --stripes(to left, powderblue, pink);
					background: --flag(progress);
				}
				@function --stripes(
				--angle: initial,
				--c0 <color>: initial,
				--c1 <color>: initial,
				--c2 <color>: initial,
				--c3 <color>: initial,
				--c4 <color>: initial,
				--c5 <color>: initial,
				--c6 <color>: initial,
				--c7 <color>: initial,
				--c8 <color>: initial,
				--c9 <color>: initial
				) {
					--n: if(
					not style(--c1): 1;
					not style(--c2): 2;
					not style(--c3): 3;
					...
				
VS Code editor showing CSS code for rendering the Progress Pride flag, alongside a generated Progress Pride flag graphic.

Computed Values Inherit

Move through DOM tree

Resolve Parent,
then Default Children

Fluid scales in CSS @function

This feature is experimental, and supported in your current browser.

  • The fluid-scale() function expects two unitless numbers, representing the pixel size to start scaling from, and the size we’ll scale to.
  • The fluid-scale() function starts from a single base size and a ratio, and assumes that each step in the scale should be fluid up to the next step in the scale. The ratio is applied to both the fluidity of a step, and also the distance between steps.
Screenshot of a code editor interface (such as CodePen) showing documentation for the CSS fluid-scale() function.

Conditional Border-Radius

Screenshot of a CodePen result window showing a button or element with rounded borders labeled "Conditional Border-Radius".

Testing @function

This feature is experimental, and supported in your current browser.

  • --to-white(--color, --step) & --to-black(--color, --step) move a color towards white or black by a given number of steps. There are 10 steps available by default, but that can be changed by defining --color-steps. The default --step is half of the total steps available.
  • --media-scheme() returns the user's preferred color-scheme.
  • --canvas(--color, --step) & --text(--color, --step) adjust a color towards Canvas or CanvasText. The default --step is the one nearest to 90%.
  • --transparent(--color, --alpha) returns a color with the alpha value adjusted.
Grid of colored rectangles in shades from black and dark purple to light pink and white, illustrating generated color palettes.
Faded lilac, still spring-like although no longer as intense as when that seductive smell first filled the air
Screenshot of a code playground (CodePen) result window displaying the rendered output of a descriptive color prompt.

You might notice...

I'm Low on Mixins

github.com/w3c/csswg-drafts/issues

https://github.com/w3c/csswg-drafts/issues (QR code linking to this URL)

Are there… Performance Issues?

Browsers Optimize Constantly

Your data is probably old

To cause issues...

Trigger Re-Style on More Elements

If you don’t need it anymore...
Then Don’t Use It

Bust if you use… Bodies, Edges, & Systems

Why make the browser Solve Server-side Problems?

Not changing
What We Can Do

If you thought CSS was Programming

If you thought CSS is Not Programming


				.meaning {
					padding: 1.5em;
					background: #d33682;
					color: #080205; }
					.meaning > h3 {
						margin: -1.5em;
						margin-bottom: 1.5em;
						padding: 1.5em;
						background: rgba(8, 2, 5, 0.5);
						color: #d33682; }
				

THE MEANING PROBLEM:

  • Repetition is hard to maintain.
  • Relationships are hidden.
  • Reasons are unclear.

If you just want...
To Code Some Styles Good

Designing With Code

Writing resilient and maintainable CSS

oddbird.dev/learn/courses/design-with-code/

miriam@oddbird.dev | oddbird.dev

QR code linking to the course page: https://oddbird.dev/learn/courses/design-with-code/

Logo for Oddbird.

  • Container Queries
  • CSS Nesting
  • CSS Color Mix
  • CSS Custom Properties
  • Table Layouts
  • Grid Systems
  • Floating Elements
  • Sass
  • CSS Grid
  • CSS Functions
  • CSS Mixins
  • CSS @Function Rule
  • CSS @Mixin Rule
  • GitHub Issues for CSSWG Drafts
  • CSS calc() Function
  • CSS Variables
  • CSS calc() with Variables
  • CSS Function Parameters
  • CSS Variable Context
  • CSS Fallback Values
  • CSS Negative Function
  • CSS Media Queries in Functions
  • CSS Gallery Mixin
  • CSS Gradient Text
  • CSS Mixins as Nested Rules
  • CSS if() Function with Media Queries
  • CSS Custom Properties and Variables
  • Sass Variable Scoping
  • CSS Value Resolution Process
  • CSS Valid Syntax
  • CSS var() Function
  • CSS Declarations with var()
  • Invalid at Computed Value Time (IACVT)
  • CSS Cascade for Variables
  • Variables Inside Functions
  • Mixins and Cascading Styles
  • CSS Defaulting Process
  • CSS Inheritance
  • CSS Initial Values
  • CSS Fallback Mechanism
  • CSS Function Types
  • Using Variables for Fallbacks
  • CSS fluid-scale() Function
  • Conditional Border-Radius
  • CSS Performance Considerations
  • CSS as a Declarative Language