Friday, September 20, 2024

“If” CSS Will get Inline Conditionals

Must read


A number of sirens went off a few weeks in the past when the CSS Working Group (CSSWG) resolved so as to add an if() conditional to the CSS Values Module Stage 5 specification. It was Lea Verou’s X submit that very same day that caught my consideration:

Lea is the one who opened the GitHub situation resulting in the dialogue and in a stroke of coincidence — or serendipity, maybe — the decision got here in on her birthday. That needed to be fairly a whirlwind of a day! What did you get on your birthday? “Oh, you realize, simply an accepted proposal to the CSS spec.” Wild, simply wild.

The accepted proposal is a inexperienced mild for the CSSWG to work on the concept with the intent of circulating a draft specification for additional enter and issues en path to, hopefully, change into a really helpful CSS characteristic. So, it’s gonna be a sizzling minute earlier than any of that is baked, that’s, if it will get totally baked.

However the concept of making use of types primarily based on a conditional requirement is tremendous thrilling and value an early take a look at the concept. I scribbled some notes about it on my weblog the identical day Lea posted to X and thought I’d distill these right here for posterity whereas rounding up extra particulars which have come up since then.

This isn’t a brand new concept

Many proposals are born from beforehand rejected proposals and if() isn’t any completely different. And, certainly, now we have gained a number of CSS options in latest days that enable for conditional styling — :has() and Container Model Queries being two of the extra apparent examples. Lea even cites a 2018 ticket that appears and reads lots just like the accepted proposal.

The distinction?

Model queries had already shipped, and we may merely reference the identical syntax for situations (plus media() and helps() from Tab’s @when proposal) whereas within the 2018 proposal how situations would work was largely undefined.

Lea Verou, “Inline conditionals in CSS?”

I like how Lea factors out that CSS goes on to explain how CSS has all the time been a conditional language:

People… CSS had conditionals from the very starting. Each selector is actually a conditional!

Lea Verou, “Inline conditionals in CSS?”

True! The Cascade is the automobile for evaluating selectors and matching them to HTML components on a web page. What if() brings to the desk is a technique to write inline situations with selectors.

Syntax

It boils right down to this:

<if()> = if( <container-query>, [<declaration-value>]{1, 2} )

…the place:

  • Values will be nested to supply a number of branches.
  • If a 3rd argument just isn’t supplied, it turns into equal to an empty token stream.

All of that is conceptual in the mean time and nothing is about in stone. We’re more likely to see issues change because the CSSWG works on the characteristic. However because it at present stands, the concept appears to revolve round specifying a situation, and setting one in every of two declared types — one because the “default” type, and one because the “up to date” type when a match happens.

.aspect {
  background-color:
    /* If the type declares the next customized property: */
    if(type(--variant: success),
      var(--color-green-50), /* Matched situation */
      var(--color-blue-50);  /* Default type */
    );
}

On this case, we’re searching for a type() situation the place a CSS variable known as --variant is asserted and is about to a price of success, and:

  • …if --variant is about to success, we set the worth of success to --color-green-50 which is a variable mapped to some greenish coloration worth.
  • …if --variant just isn’t set to success, we set the worth of the success to --color-blue-50 which is a variable mapped to some bluish coloration worth.

The default type can be non-compulsory, so I feel it may be omitted in some circumstances for barely higher legibility:

.aspect {
  background-color:
    /* If the type declares the next customized property: */
    if(type(--variant: success),
      var(--color-green-50) /* Matched situation */
    );
}

The syntax definition up prime mentions that we may assist a 3rd argument along with the matched situation and default type that enables us to nest situations inside situations:

background-color: if(
  type(--variant: success), var(--color-success-60), 
    if(type(--variant: warning), var(--color-warning-60), 
      if(type(--variant: hazard), var(--color-danger-60), 
        if(type(--variant: major), var(--color-primary)
      )
    ),
  )
);

Oomph, seems like some wild inception is occurring in there! Lea goes on to recommend a syntax that may lead to a a lot flatter construction:

<if()> = if( 
  [ <container-query>, [<declaration-value>]{2}  ]#{0, },
  <container-query>, [<declaration-value>]{1, 2} 
)

In different phrases, nested situations are rather more flat as they are often declared exterior of the preliminary situation. Similar idea as earlier than, however a unique syntax:

background-color: if(
  type(--variant: success), var(--color-success-60), 
  type(--variant: warning), var(--color-warning-60),
  type(--variant: hazard), var(--color-danger-60), 
  type(--variant: major), var(--color-primary)
);

So, reasonably than one if() assertion inside one other if() assertion, we are able to lump the entire doable matching situations right into a single assertion.

We’re trying to match an if() situation by querying a component’s types. There isn’t any corresponding measurement() operate for querying dimensions — container queries implicitly assume measurement:

.aspect {
  background: var(--color-primary);

  /* Situation */
  @container dad or mum (width >= 60ch) {
    /* Utilized types */
    background: var(--color-success-60);
  }
}

And container queries change into type queries after we name the type() operate as an alternative:

.aspect {
  background: orangered;

  /* Situation */
  @container dad or mum type(--variant: success) {
    /* Utilized types */
    background: dodgerblue;
  }
}

Model queries make much more sense to me after they’re seen within the context of if(). With out if(), it’s straightforward to query the overall usefulness of fashion queries. However on this mild, it’s clear that type queries are a part of a a lot greater image that goes past container queries alone.

There’s nonetheless loads of issues to suss out with the if() syntax. For instance, Tab Atkins describes a doable state of affairs that might result in confusion between what’s the matched situation and default type parameters. So, who is aware of how this all shakes out ultimately!

Circumstances supporting different situations

As we’ve already famous, if() is way from the one sort of conditional examine already supplied in CSS. What would it not seem like to write down an inline conditional assertion that checks for different situations, similar to @helps and @media?

In code:

background-color: if(
  helps( /* and many others. */ ),
  @media( /* and many others. */ )
);

The problem can be container supporting measurement queries. As talked about earlier, there is no such thing as a specific measurement() operate; as an alternative it’s extra like an nameless operate.

@andruud has a succinctly describes the problem within the GitHub dialogue:

I don’t see why we couldn’t do helps() and media(), however measurement queries would trigger cycles with structure which might be arduous/not possible to even detect. (That’s why we wanted the restrictions we at present have for measurement CQs within the first place.

“Can’t we already do that with [X] strategy?”

After we had been trying on the syntax earlier, you could have seen that if() is simply as a lot about customized properties as it’s about conditionals. A number of workarounds have emerged through the years to imitate what we’d acquire if() we may set a customized property worth conditionally, together with:

  • Utilizing customized properties as a Boolean to use types or not relying on whether or not it is the same as 0 or 1. (Ana has an exquisite article on this.)
  • Utilizing a placeholder customized property with an empty worth that’s set when one other customized property is about, i.e. “the customized property toggle trick” as Chris describes it.
  • Container Model Queries! The issue (moreover lack of implementation) is that containers solely apply types to their descendants, i.e., they can’t apply types to themselves after they meet a sure situation, solely its contents.

Lea will get deep into this in a separate submit titled “Inline conditional statements in CSS, now?” that features a desk that outlines and compares approaches, which I’ll merely paste beneath. The reasons are filled with advanced CSS nerdery however are extraordinarily useful for understanding the necessity for if() and the way it compares to the intelligent “hacks” we’ve used for years.

Technique Enter values Output values Execs Cons
Binary Linear Interpolation Numbers Quantitative Can be utilized as a part of a price Restricted output vary
Toggles var(--alias) (precise values are too bizarre to show uncooked) Any Can be utilized in a part of a price Bizarre values that must be aliased
Paused animations Numbers Any Regular, decoupled declarations Takes over animation property

Cascade weirdness

Kind Grinding Key phrases Any worth supported by the syntax descriptor Excessive flexibility for uncovered APIGood encapsulation Should insert CSS into mild DOM

Tedious code (although will be automated with construct instruments)

No Firefox assist (although that’s altering)

Variable animation identify Key phrases Any Regular, decoupled declarations Impractical exterior of Shadow DOM as a result of identify clashes

Takes over animation property

Cascade weirdness

Joyful birthday, Lea!

Belated by two weeks, however thanks for sharing the spoils of your massive day with us! 🎂

References

Direct Hyperlink →





Supply hyperlink

More articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest article