A few sirens went off a couple of weeks ago when the CSS Working Group (CSSWG) resolved to add an if()
conditional to the CSS Values Module Level 5 specification. It was Lea Verou’s X post that same day that caught my attention:
A historical day for CSS 😀🎉
— Lea Verou (@LeaVerou) June 13, 2024
If you write any components used and/or styled by others, you know how huge this is!
background: if(style(–variant: success), var(–green));
Even if you don’t, this will allow things like:
padding: if(var(–2xl), 1em, var(–xl) or var(–m),… pic.twitter.com/cXeqwBuXvK
Lea is the one who opened the GitHub issue leading to the discussion and in a stroke of coincidence — or serendipity, perhaps — the resolution came in on her birthday. That had to be quite a whirlwind of a day! What did you get for your birthday? “Oh, you know, just an accepted proposal to the CSS spec.” Wild, just wild.
The accepted proposal is a green light for the CSSWG to work on the idea with the intent of circulating a draft specification for further input and considerations en route to, hopefully, become a recommended CSS feature. So, it’s gonna be a hot minute before any of this is baked, that is, if it gets fully baked.
But the idea of applying styles based on a conditional requirement is super exciting and worth an early look at the idea. I scribbled some notes about it on my blog the same day Lea posted to X and thought I’d distill those here for posterity while rounding up more details that have come up since then.
This isn’t a new idea
Many proposals are born from previously rejected proposals and if()
is no different. And, indeed, we have gained several CSS features in recent days that allow for conditional styling — :has()
and Container Style Queries being two of the more obvious examples. Lea even cites a 2018 ticket that looks and reads a lot like the accepted proposal.
The difference?
Style queries had already shipped, and we could simply reference the same syntax for conditions (plus
Lea Verou, “Inline conditionals in CSS?”media()
andsupports()
from Tab’s@when
proposal) whereas in the 2018 proposal how conditions would work was largely undefined.
I like how Lea points out that CSS goes on to describe how CSS has always been a conditional language:
Folks… CSS had conditionals from the very beginning. Every selector is essentially a conditional!
Lea Verou, “Inline conditionals in CSS?”
True! The Cascade is the vehicle for evaluating selectors and matching them to HTML elements on a page. What if()
brings to the table is a way to write inline conditions with selectors.
Syntax
It boils down to this:
<if()> = if( <container-query>, [<declaration-value>]{1, 2} )
…where:
- Values can be nested to produce multiple branches.
- If a third argument is not provided, it becomes equivalent to an empty token stream.
All of this is conceptual at the moment and nothing is set in stone. We’re likely to see things change as the CSSWG works on the feature. But as it currently stands, the idea seems to revolve around specifying a condition, and setting one of two declared styles — one as the “default” style, and one as the “updated” style when a match occurs.
.element {
background-color:
/* If the style declares the following custom property: */
if(style(--variant: success),
var(--color-green-50), /* Matched condition */
var(--color-blue-50); /* Default style */
);
}
In this case, we’re looking for a style()
condition where a CSS variable called --variant
is declared and is set to a value of success
, and:
- …if
--variant
is set tosuccess
, we set the value ofsuccess
to--color-green-50
which is a variable mapped to some greenish color value. - …if
--variant
is not set tosuccess
, we set the value of thesuccess
to--color-blue-50
which is a variable mapped to some bluish color value.
The default style would be optional, so I think it can be omitted in some cases for slightly better legibility:
.element {
background-color:
/* If the style declares the following custom property: */
if(style(--variant: success),
var(--color-green-50) /* Matched condition */
);
}
The syntax definition up top mentions that we could support a third argument in addition to the matched condition and default style that allows us to nest conditions within conditions:
background-color: if(
style(--variant: success), var(--color-success-60),
if(style(--variant: warning), var(--color-warning-60),
if(style(--variant: danger), var(--color-danger-60),
if(style(--variant: primary), var(--color-primary)
)
),
)
);
Oomph, looks like some wild inception is happening in there! Lea goes on to suggest a syntax that would result in a much flatter structure:
<if()> = if(
[ <container-query>, [<declaration-value>]{2} ]#{0, },
<container-query>, [<declaration-value>]{1, 2}
)
In other words, nested conditions are much more flat as they can be declared outside of the initial condition. Same concept as before, but a different syntax:
background-color: if(
style(--variant: success), var(--color-success-60),
style(--variant: warning), var(--color-warning-60),
style(--variant: danger), var(--color-danger-60),
style(--variant: primary), var(--color-primary)
);
So, rather than one if()
statement inside another if()
statement, we can lump all of the possible matching conditions into a single statement.
This is all related to style queries
We’re attempting to match an if()
condition by querying an element’s styles. There is no corresponding size()
function for querying dimensions — container queries implicitly assume size:
.element {
background: var(--color-primary);
/* Condition */
@container parent (width >= 60ch) {
/* Applied styles */
background: var(--color-success-60);
}
}
And container queries become style queries when we call the style()
function instead:
.element {
background: orangered;
/* Condition */
@container parent style(--variant: success) {
/* Applied styles */
background: dodgerblue;
}
}
Style queries make a lot more sense to me when they’re viewed in the context of if()
. Without if()
, it’s easy to question the general usefulness of style queries. But in this light, it’s clear that style queries are part of a much bigger picture that goes beyond container queries alone.
There’s still plenty of things to suss out with the if()
syntax. For example, Tab Atkins describes a possible scenario that could lead to confusion between what is the matched condition and default style parameters. So, who knows how this all shakes out in the end!
Conditions supporting other conditions
As we’ve already noted, if()
is far from the only type of conditional check already provided in CSS. What would it look like to write an inline conditional statement that checks for other conditions, such as @supports
and @media
?
In code:
background-color: if(
supports( /* etc. */ ),
@media( /* etc. */ )
);
The challenge would be container supporting size queries. As mentioned earlier, there is no explicit size()
function; instead it’s more like an anonymous function.
@andruud has a succinctly describes the challenge in the GitHub discussion:
I don’t see why we couldn’t do
supports()
andmedia()
, but size queries would cause cycles with layout that are hard/impossible to even detect. (That’s why we needed the restrictions we currently have for size CQs in the first place.
“Can’t we already do this with [X] approach?”
When we were looking at the syntax earlier, you may have noticed that if()
is just as much about custom properties as it is about conditionals. Several workarounds have emerged over the years to mimic what we’d gain if()
we could set a custom property value conditionally, including:
- Using custom properties as a Boolean to apply styles or not depending on whether it is equal to
0
or1
. (Ana has a wonderful article on this.) - Using a placeholder custom property with an empty value that’s set when another custom property is set, i.e. “the custom property toggle trick” as Chris describes it.
- Container Style Queries! The problem (besides lack of implementation) is that containers only apply styles to their descendants, i.e., they cannot apply styles to themselves when they meet a certain condition, only its contents.
Lea gets deep into this in a separate post titled “Inline conditional statements in CSS, now?” that includes a table that outlines and compares approaches, which I’ll simply paste below. The explanations are full of complex CSS nerdery but are extremely helpful for understanding the need for if()
and how it compares to the clever “hacks” we’ve used for years.
Method | Input values | Output values | Pros | Cons |
---|---|---|---|---|
Binary Linear Interpolation | Numbers | Quantitative | Can be used as part of a value | Limited output range |
Toggles | var(--alias) (actual values are too weird to expose raw) | Any | Can be used in part of a value | Weird values that need to be aliased |
Paused animations | Numbers | Any | Normal, decoupled declarations | Takes over animation propertyCascade weirdness |
Type Grinding | Keywords | Any value supported by the syntax descriptor | High flexibility for exposed APIGood encapsulation | Must insert CSS into light DOM Tedious code (though can be automated with build tools) No Firefox support (though that’s changing) |
Variable animation name | Keywords | Any | Normal, decoupled declarations | Impractical outside of Shadow DOM due to name clashes Takes over animation propertyCascade weirdness |
Happy birthday, Lea!
Belated by two weeks, but thanks for sharing the spoils of your big day with us! 🎂
References
- What is the MVP for inline conditionals on custom properties? (Lea Verou, GitHub Issue #10064)
- Inline conditionals in CSS? (Lea Verou)
- Inline conditionals in CSS, now? (Lea Verou)
Apart from not needing anything like this, this will be a terrible day for CSS if it gets implemented. The good thing about CSS is that it’s still relatively unscathed by idiot programmer influences – but adding this will open the floodgates to all sorts of dumb stuff.
The next thing you know some arsehole will code a web server out of CSS and this will usher in a new era of super abstracted, inscrutable UI frameworks that will require a some kind of node extension to run and will require a web server restart everytime you make a change, further preventing devs from actually learning CSS.
If there’s a developer out there that actually thinks this is a win for CSS then they probably shouldn’t be writing CSS. Stick to Java or C because you wouldn’t know how to code layout if your life depended on it.
It has been done https://dev.to/thormeier/dont-try-this-at-home-css-as-the-backend-what-3oih
I hand never needed to use if conditions in css. I think this goes against what css is meant for, which is a cascading stylesheet… cascading being the key weird here. This is further complicating a simple and beauty language into something it’s not meant to be. KISS
Agree with all comments, utterly strange hype for a completely pointless abstraction.
What pray tell will set the success variable value true or false?
Yeah the same code that worked to toggle a classname or conditionally alter the styles currently.
All this does is confuse devs on where to look if bugs occur since functionality could be in css or code now.
It also will make testing functionality just a tad more difficult.
My brain hurts!
However, I’ve used conditionals in my Sass code for years. For example, passing args into mixins, and testing for their values.
Hopefully this native behaviour will become a reality one day… but without creating some sort of “Frankenstein” syntax to achieve it.
Some other comments cover almost everything I hate about working in this field. They are shortsighted, opinionated, solely based upon one’s very own needs and some are even hostile against others.
Have you even tried to look at it from other viewpoints? Have you tried to find practical use-cases for it outside your current bubble? One of the benefits could well be to finally not be dependent on certain types of CSS/JS frameworks anymore. It could be the next step towards a vanilla web development. Towards easier to implement styled web components. Towards easy to set up, fully customizable theming. Towards even more DRY, compact CSS.
Honestly, I see a lot more pros than cons. My cons being more on the side of performance and syntax. But those are exactly the things the working group is for, to either sort those out or say “nah, not worth the effort”.
Those comments look like hasty calls for stagnation; change isn’t welcome. Would this mindset be prevalent, we would still be sitting in front of CRTs, use the bgcolor attribute and do table layouts like in the late 1990s, early 2000s. Ah, strike that! We would probably still paint cave walls with ochre paints.
Also, in regard to the hostile comment mentioned above: Calling others idiots, arseholes or dumb. Really? Someone’s in need for self validation here?