Saw this from Lea being passed around yesterday:
A historical day for CSS šš
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 Verou (@LeaVerou) June 13, 2024
Whoa, right?! Seeing an :if() function in CSS gets my mind going in all sorts of directions. But what Lea is referring to is a GitHub issue she opened in the CSS Values Module Level 5 repo. I thought Iād take some notes on that discussion as I try to wrap my mind around whatās going on.
This is about conditional statements, but mostly custom propertiesThe ticket describes a long-running desire for developers to apply style if() a certain condition is true. There are already some workarounds that get us close:
0 or 1. (Ana has a wonderful article on this.)SyntaxLea isnāt proposing a single, particular direction but rather is poking at what might the minimum viable approach to get us to conditional statements, one that can be extended later as needs and ideas arise.
That said, hereās one possible way to define what if() might look like:
<if()> = if( <container-query>, [<declaration-value>]{1, 2} )
ā¦where:
As I best understand it ā which may be very little ā we identify a container query (just style(), I think?) the declared style condition we want to match and set style with one of two declared styles, one as the default style, and one as the updated style when a match occurs.
.element { background-color: if(style(--variant: success), var(--color-green-50), /* Matched condition */ var(--color-blue-50); /* Default style */ );}
The default style would be optional, so I think it can be omitted in some cases:
.element { background-color: if(style(--variant: success), var(--color-green-50) /* Matched condition */ );}
Nesting conditionsLea offers an example of how a nested set of conditions might work as a third possible argument:
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) ) ), ));
ā¦but goes on to suggest a syntax that would make this less verbose and meta.
<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 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));
What about other types of checks?I love this question Lea poses:
How hard would it be to support other queries beyond
style()? E.g. size queries,supports(),media()? (style()does address the vast majority of use cases, but if any of these are easy, we may as well support them too.)
So:
background-color: if( supports( /* etc. */ ), @media( /* etc. */ ));
Neat-o idea! The challenge would be container size queries. As currently specced and implemented, there is no size() function; instead itās more like an anonymous function:
@container (width > 65ch) { /* Style rulesets */ }
Iāve tried to understand the limitations of size queries before, but admit itās just a lot for this blondie to grok. Whatever the case, @andruud has a succinct answer:
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.
Commas, colons, semicolons, or question marks?After the Working Group discussed the proposal in a meeting, more was raised on the specifics of the syntax, namely what characters make the most sense and for the most readable code.
True to form, Tab Atkins sums it up nicely:
if(style(...): foo, bar)could be a single valuefoo, bar(and IACVT if false), or two values (fooif true,barif false). This does have a defined parse ā itās always two values. But if you wanted it to be one value, and then IACVT for false, you canāt write that.(This ambiguity doesnāt occur if there are multiple chained conditions.
if(style(...): foo; style(...): bar, baz)unambiguously doesnāt have a default case; thebar, bazis the whole value for the second condition. Itās only in the single-condition case that we havenāt yet gotten unambiguous information about what the separator is.)
First thing to note there is the addition of a colon following the style() function in place of the comma that was initially proposed:
background: if( style(...): /* instead of `style(...),` */ foo, bar )
The second thing to note is a possible issue with using commas to separate declaration values:
if( style(...): foo, bar)
That could confuse what we mean. Is it that foo and bar are separate values, where one is the default style and the other is the updated style? Or are we defining one compound value, foo, bar that is applied when the condition is false? In other words, can we omit a default value if needed and still have the condition properly evaluated?
Tab suggests a way to say āno default conditionā in the syntax, e.g.:
if( style(...): foo, bar; no-value)
Lea suggests we might be able to use a different operator to separate the style condition from the declaration values, such as ? or : so that itās possible to make the last argument mandatory while accepting an empty value there:
if(style(...) ? foo)
If not that, then maybe a keyword to denote no default as the last argument:
if(style(...) ? foo, no-value)
So, whereās this at?Leaās X post provides this example:
background: if(style(--variant: success), var(--green));
As well as this one to illustrate (Iām nesting it to help me read it):
padding: if( var(--2xl), 1em, var(--xl) or var(--m), .5em);
Followed, of course, by a screenshot that the Working Group resolved to add the if() function to the specification.
There seems to be a lot more nuance than what Iām capturing here, but these are just my notes for general understanding and keep an eye on how it all shakes out.