Csswg-drafts: [mediaqueries][css-conditional] else

Created on 13 May 2016  ·  55Comments  ·  Source: w3c/csswg-drafts

Update: Current proposal is for an @when and @else rule, and is located at https://tabatkins.github.io/specs/css-when-else/


From @frivoal:

During the face to face, we talked about the fact setting default styles outside of a media query, then undoing then in the media query before applying new ones is painful, and that authors often want to do something like if/else.

I suggest we go with this syntax:

@media (something) {
 ...
 ...
 ...
 @else {
 }
}

@supports (foo:bar) {
 ...
 ...
 ...
 @else {
 }
}

I think putting the else inside the conditional rather than following it, is more consistent with how things work in CSS, since we don't currently have a concept of associating two separate @rules.

The transition phase isn't too nice, as this isn't really usable until all browsers support it, but I think this is going to be the case for any syntax we can pick for this feature.

Thoughts?

css-conditional-4 mediaqueries-5

Most helpful comment

If we're wanting to start doing this kind of thing, people _will_ want to do things like, say, test for both an @media _and_ an @supports, and then have a block of code for when they're both true and both false. This means we'll want to make them combinable in a reasonable way.

Maybe instead, we just explicitly create an @if block, which can take media() and supports() conditions (like what we have for @import statements). @media/@supports would then just be special-case versions that let you write slightly shorter blocks when you only need one or the other type. Then @else (and @else-if) can be blocks that have to follow an @if or equivalent.

That is, you can start an if-chain with an @if, @media, or @supports block, then continue it with zero or more @else-if blocks (with grammar identical to @if), then an optional final @else block with no condition. You can be guaranteed that exactly one of the blocks in the chain will be evaluated.

All 55 comments

I don't think there's a _problem_ with linking an @else to an immediately preceding conditional rule. It's new, but eh.

Problem with nesting, to me, is that it makes it feel like it might negate _everything_? Like, if you have @media ➀ { @supports ➁ { @else { ➂ } } }, it's not super clear whether the @else stuff applies solely when ➀ and !➁ (just negates the closest condition), or when !(➀ and ➁) (negates the current condition stack).

As a web developer, I'd prefer not putting it inside, which looks weird, and doesn't match the intuition from any other programming language.

As a browser developer, though, I'm concerned about how CSSOM should handle this.

Also I guess it makes sense to allow @else to precede another @media or @support, like a if-elseif structure. But that would likely make story of CSSOM more complicated.

If we're wanting to start doing this kind of thing, people _will_ want to do things like, say, test for both an @media _and_ an @supports, and then have a block of code for when they're both true and both false. This means we'll want to make them combinable in a reasonable way.

Maybe instead, we just explicitly create an @if block, which can take media() and supports() conditions (like what we have for @import statements). @media/@supports would then just be special-case versions that let you write slightly shorter blocks when you only need one or the other type. Then @else (and @else-if) can be blocks that have to follow an @if or equivalent.

That is, you can start an if-chain with an @if, @media, or @supports block, then continue it with zero or more @else-if blocks (with grammar identical to @if), then an optional final @else block with no condition. You can be guaranteed that exactly one of the blocks in the chain will be evaluated.

@tabatkins While your @if/@else idea sounds good, it would probably be more clear and unambiguous if you’ve provided a code example besides just a description.

@Marat-Tanalin, I take it to mean something like this:

@if media((width >= 400px) and (pointer: fine)) and supports(display: flex) {
   ...
}
@else-if supports((caret-color: pink) or (background: double-rainbow()){
  ...
}
@else {
  ...
}

I do think "else-if" is almost as important as "else", whatever the syntax.

Would @else have to come immediately after an @if or @else-if block, or could there be rules in between? I would think the former. The latter could be useful for organizing code, but would probably lead to mistakes.

Would @else have to come immediately after an @if or @else-if block

Yes, the alternative is too error prone and non obvious.

Sorry for the delay - Florian's example is indeed exactly what I intend.

@bradkemper Yeah, definitely immediately after. A "dangling @else" (an @else or @else-if whose preceding rule was not a conditional or @else-if) would be invalid. It's _technically_ non-ambiguous to allow interleaved rules, but it would be terrible for readability.

Combinable queries seem pretty useful. The current W3C style sheet is doing a conditional against both selectors and @media, which is involving a fair bit of code duplication atm...

@fantasai I'd love to see an example of that - you're talking about mixing selectors and MQs, which isn't even on the table yet.


I've produced a draft spec for this at https://tabatkins.github.io/specs/css-when-else/. If we accept this, I intend it to be merged into Conditional.

@tabatkins I've skimmed your suggested spec, and modulo some fleshing out of details (particularly the parsing and evaluation of <boolean-condition>), I approve.

See https://www.w3.org/StyleSheets/TR/2016/base.css section marked "ToC Sidebar".

@fantasai Looks like it's where you want "if the screen is narrow _or_ body has .toc-inline, use the inline styling; if screen is wide _or_ body has .toc-sidebar, use the sidebar styling"?

Right

So I discussed this with the Sass folks today, and @if clashes with their existing rule. @else-if and @else are fine. We did some brainstorming, and I think @when works pretty well.

So we'd have @when as the combined conditional rule, @else-when for extending, and @else for finishing.

Other possibilities brought up were @test, @cond, @where.

@fantasai So if we pretend there's a selector() function that is true if something on the page matches that selector, I think you'd write:

@when selector(body.toc-inline) or (media(width < 28em) and not selector(body.toc-sidebar)) {
  /* inline ToC styling */
} @else {
  /* sidebar ToC styling */
}

Fwiw, in the past, a SASS developer said at www-style that existing features of SASS should not be considered as limitations for new CSS features in any way:

if a potential new CSS feature is conflicting with an existing SASS feature, then the latter could just be changed.

By the way, why not just extend @media to use it itself as @if?

Yeah, Sass usage does not trump CSS. But there's also no reason to be hostile to the developer community when we can easily avoid such. In this case, I think there are several names that work just fine in place of @if.

By the way, why not just extend @media to use it itself as @if?

Are you asking why @media can't be used at the start of a conditional chain? It can be - the conditional chain just needs to be started by any conditional group rule other than @else-when or @else.

Or are you asking why we don't just extend @media to have the full value set that I've defined @when to have? That's because it would be weird; there's nothing media-like about supports queries, or about other kinds of queries we might do in the future (like the @url rule). It also would make the grammar very complicated.

Yeah, Sass usage does not trump CSS. But there's also no reason to be hostile to the developer community when we can easily avoid such.

Replacing the clear straightforward keyword widely used in almost all languages with an invented more-or-less similar one is actually probably not that _easy_.

At the same time, SASS (as well as any other preprocessor) could really easily rename its @if or just transparently convert something like @css-if or @native-if to native CSS @if. That’s not a CSS problem at all.

CSSWG decisions should probably not be based on the goal of avoiding preprocessor conflicts. CSS is a much more fundamental thing that require carefully weighed decisions reasonable and usable in the long term. Preprocessors can be changed at any time, CSS cannot at all.

Or are you asking why we don't just extend @media to have the full value set that I've defined @when to have?

This one.

it would be weird; there's nothing media-like about supports queries

We already have not quite media-related things like scripting in media queries. After all, extending @media is (except for placing @else as a subrule) what Florian has initially proposed.

It also would make the grammar very complicated.

I’m not sure why this:

@media ((width >= 400px) and (pointer: fine)) and supports(display: flex) {}

would be more complicated than this (at least from author’s [not spec writer’s] perspective):

@if media((width >= 400px) and (pointer: fine)) and supports(display: flex) {}

The media code could even be simpler without redundant parens:

@media (width >= 400px) and (pointer: fine) and supports(display: flex) {}

If we were, today, trying to invent supports queries, and someone suggested just putting them into our sole existing conditional rule (@media), I'd be receptive. But we aren't - we already have two specialized conditional rules that handle different kinds of things (and a third that was proposed and exists in Firefox user-level stylesheets), so it's weird to privilege one of them.

Also, media queries have a weird legacy syntax with their media types that makes this a little stranger. Overall not that great. Starting fresh and putting all the types of queries on an equal footing is simple and easy.

@tabatkins between the alternative you proposed, I like @when best. @cond is nice for people who know lisp, but that's not a whole lot of people, and everyone else is going to find it obscure. @test or @where aren't necessarily that bad, but I like them less.

I'm also in favor of allowing @else after any kind of conditional rule (@media, @supports, @when, @document if we ever get it...)

I'd prefer @if... not very strongly, though.

I agree with @Marat-Tanalin that we should not pick a weird syntax just to avoid conflicting with a preprocessor, and preprocessors can always work around that easily, either via a breaking change stated in the release note or introducing a new keyword.

I can imagine that if we choose @when and @else-when, more developers would tend to critize that CSSWG is making their life harder via adding some syntax different than what they are familiar with, rather than appreciating the work of avoiding the imaginary breakage.

I think the criteria is, is there more web developers use SASS's @if than those who do not use SASS's @if (or do not use SASS at all) but familiar with the general if-else structure? What's the percentage?

IMHO Sass is an incumbent technology, so purposely colliding with it is akin to "breaking the web". This issue ring similar to the array.contains issue faced by TC39.

Sass will get out of the way as much as possible, but an @if like this could potentially force a python 3 style split that community.

What's the percentage?

Hard to say for sure. A couple recent developer surveys put Sass usage at about 60%-70% of respondents.

preprocessors can always work around that easily

This is generally true for when CSS does something new that preprocessors didn't previously do, like custom properties for example.

However in the CSS of adopting an existing syntax there is no good work around for preprocessor authors. Best case they release a new major with a new syntax. Doing so breaks the existing community of packages.

Even if all package were updated, there is always a significant percentage of people who cannot update for reasons like internal processes, or software. These users are affected the worse because they are left completely unable to use this feature.

This issue ring similar to the array.contains issue faced by TC39.

This isn't. The original Sass file before preprocessing is not processed directly by browsers.

However in the CSS of adopting an existing syntax there is no good work around for preprocessor authors. Best case they release a new major with a new syntax.

Workaround shouldn't be too hard. As @Marat-Tanalin mentioned, they can keep their existing @if and introduce a new keyword like @css-if which is converted to the CSS's @if during preprocessing. What's the problem with this workaround?

The original Sass file before preprocessing is not processed directly by browsers.

True, but it leaves the author unable to use the CSS @if syntax.

new keyword like @css-if which is converted to the CSS's @if

Also true. This shifts the burden off from the existing ecosystem and onto the everyday developers.


Aside from technology I think there are real concerns regarding the effects on the wider CSS and Sass communities, some potential examples:

Raising the barrier to entry by negatively affecting google search results for the correct syntax for a given context i.e. authoring in CSS or Sass.

Causing confusion where-in blog authors would need to explicitly declare CSS @if or Sass @if in their content.

Actually it seems to me it is even possible for preprocessors to integrate the new CSS @if with their existing one, so that authors wouldn't need to distinguish between the two @ifs, and CSS's @if would be generated when the preprocessor thinks it should do.

Since CSS's @if is somehow a syntax sugar of existing syntax, the preprocessors can generate the old syntax if requested before all browsers ship the support of the new syntax.

it is even possible for preprocessors to integrate the new CSS @if with their existing one

I don't believe this is possible. Just about every part of the proposed syntax collides with Sass.

The breakpoint and supports functions are common user defined functions in all major preprocessors. Given that I'm not sure if it's possible disambiguate between the Sass and CSS @if.

Where as the earlier @media (..) { } @else { }, and by extension the suggested @when (...) { } @else { } is unambiguous.

when the preprocessor thinks it should do

This comes off a bit hand-wavey, although I'd be curious to hear how you think the preprocessor would know. This is something Sass has talked about doing for @extends -> matches() some day.

For the sake of transparency I should note I work on the LibSass and Node Sass projects. However my concerns are largely for the community impacts. I don't have a dog in the race with regards to technology.

is there more web developers use SASS's @if than those who do not use SASS's @if (or do not use SASS at all) but familiar with the general if-else structure?

Even more, it’s probably a matter of how many users not just use SASS/SCSS, but also strongly depend on third-party libraries of SASS mixins that are using if, and cannot stick with the current SASS implementation until having time/desire to update their outdated SASS style code with a newer one.

Fwiw, as a web developer, I use the @if feature of SCSS (via scssphp) very rarely, as well as I don’t use libraries of SASS mixins (except for those written by me and thus available to be updated by myself at any moment).

Okay, it seems I misunderstood how that directive works, and I agree it looks hard to integrate them or distinguish between those two kinds of @if for preprocessors.

But it could still be solved by adding some new syntax to make it future-proof with changes in CSS. For example, say Sass can probably make @+something be converted to @something in CSS, so that Sass can use any name as the directive without concerning / affecting change in CSS's at-rules.

That still adds burden to everyday developers, but that only adds one extra character burden for people who use Sass, while @when brings two extra characters for everyone, plus the mental burden from unfamiliarity.

But I agree the raised barrier from search results could be a real concern. Not sure how bad that would be, though.

Sass can probably make @+something be converted to @something

I agree that some sort of escape hatch could be useful. I'd argue the number of keystrokes is a non-issue, the issue is the cognitive overhead. An escape hatch like this is nice because it's the same for all directives so it becomes just part of the language.

It's interesting to note Sass has previously relied on an escape hatch that CSS has provided with the - prefix. We were able to take advantage of this even recently with the extra - in the prefix for custom properties.

Funnily enough the technology aspect of this issue go away with @-if.

while @when [..] the mental burden from unfamiliarity.

Given that CSS devs have never had an if construct (outside of preprocessors) I'd argue that unfamiliarity is moot point, and any one who is familiar if constructs would easily grasp an alternative syntax.

Plus they get the added bonus but @when would be Googleable.

there's nothing media-like about supports queries

Think of non-media part of query as an _extra condition_ instead of a part of the media query _itself_.

The @supports could also be extended based on the same paradigm. So the following:

@media (width >= 400px) and (pointer: fine) and supports(display: flex) {}
@supports (display: flex) and media((width >= 400px) and (pointer: fine)) {}

would be perfectly interchangeable equivalents (so we have media and supports in the former and supports and media in the latter, like 2+3 is the same as 3+2).

Along the advantages of this approach are that the redundant parens around the main part of query are avoided, as well as that the philosophical @when-or-@if question gets then irrelevant.

If needed, we could make the main part (related to the rule name) required to be the _first part_ of the query, and require the optional extra condition to follow it.

Again, it's possible, but I don't like it very much. Particularly since you'd then have the same MQ bias for the @else rule, where it's much less obvious. This feels like the sort of "legacy things evolved into what we have today" awkwardness that infests much of the web platform.

An argument in favor is that this is the syntax we used for @import's condition, since it was specialized to be a naked MQ way back when. But again, that's "legacy awkwardness we evolved around" - if we'd designed @import conditions today, I'm pretty sure we'd have media() and supports() functions, or something similar.

Suppose an author wants to use a media query without nonmedia conditions. They use a @media rule since compared with @if or @when it is easier to write (less parens) and more readable. Then, after some time, the author needs to add an additional condition: they could just append it to the end of the existing @media preamble instead of rewriting it and wrapping the preamble in parens.

Btw, does GitHub have a poll feature?

(less parens)

Exactly the same number of parens. Extra 5 characters (the media at the start of each parenthesis set) per MQ, _or_ a static 7 characters (media(...)) around the whole thing.


Alternate scenario: the author has written an @supports rule, and realizes they need an @else condition. They try to write @else (float: left) {...}, but it fails for no obvious reason, because the CSSWG decided that @media was the generic form and @else has to follow - they instead have to remember to write @else supports(float: left) {...}.

There are minor usability problems both ways. Designing toward explicitness makes it easier to learn and remember, tho. This is a lesson I've learned _many_ times watching features get designed.

Exactly the same number of parens

Am I the only who sees a couple of extra parens in the first line compared with the second one?

@if media((width >= 400px) and (pointer: fine)) {}
@media (width >= 400px) and (pointer: fine) {}

They try to write @else (float: left) {...}, but it fails for no obvious reason

For @else, an exact explicit function like media() or supports() could probably be mandatory. The @when-or-@if issue would probably still be ruled out anyway.

The way I wrote that example includes one. It's not required. You can also write it as:

@if media(width >= 400px) and media(pointer: fine)) {}
@media (width >= 400px) and (pointer: fine) {}

For @else, an exact explicit function like media() or supports() could probably be mandatory. The @when-or-@if issue would probably still be ruled out anyway.

Then you have to recognize a _third_ grammar, which is still awkward as heck.

Rather than "@media allows naked MQs and the supports() function, @supports allows naked support queries and the media() function, @else requires both media() and supports()", we can instead say @media allows MQs, @supports allows support queries, @when and @else require media() and supports() (because neither is clearly a particular type of conditional).

Just thinking out loud, we have not in MQs and it's doing precisely what you want, right?-)

@media screen and (color) { /* your @when statements */ }
@media not screen and (color) { /* your @else statements */ }

@glazou, yes, it sort of works, except that:

  1. when your MQ is long and complicated, that ends up being a lot of stuff to repeat, which is tedious at best, and a source of bugs when you make a mistake somewhere. You may get it right on the first pass, but it is decently likely that you'll make a mistake during maintenance by forgetting to update the second half.
  2. explicit negation works OKish for if/else, but it is even more tedious when you try to do some if / elseif / else.
  3. both branches are ignored if you do
@media screen and (unsupported-thing) { }
@media not screen and (unsupported-thing) { }

@frivoal I perfectly understand the readability issue, but I'm thinking of the editability issue in automated environements like a Wysiwyg editor. It's already INCREDIBLY painful to deal with arbitrary MQs and @else statements will make them even more complicated to deal with...

It also doesn't handle combined @media and @supports conditions.

Just on the issue of @when vs @if vs @cond:

In English as in German when does not necessarily introduce a timeless condition (Edit: and in CSS we should, IMHO).

So cond or if are better in terms of nomenclature compared to when which is a bit like using then instead of than. @where, @match, @case would all IMHO preferable to @when.

What do you mean by "does not necessarily introduce a timeless condition"? One reason I like "when" is that it explicitly invokes time, rather than looking like an imperative, once-and-done construct. "Whenever X is true, do Y.", to me, helps suggest that the condition will turn on and off over the lifetime of the page.

As I consider CSS to be more on the declarative than imperative side, if is timeless then IMHO.

<pattern> => application
@if(condition) { apply block }
selector_or_pattern { apply block }

In my book when is strictly imperative/process oriented, if/cond are more declarative/pattern-matching/timeless
When: "As soon as X, do Y"
If: "Whenever X is the case, Y is the case"
but... cond, match, where may also work and not clash with SCSS/SASS?!

Things turning on and off happens through user-interaction/behaviors/javascript/keyframes and those external processes simply set a different state (say window size or dom tree change) which "in theory" the css declarations that pattern match the conditions applies instantly without any delay (in theory!).

Anyway I'd 👍 by a big margin even using @when

@tabatkins -

Per my thoughts on scoping/namespace usage in issue #270 there's a potential reason to consider @if and @when separately.

inoas mentions 'timeless conditions'. @if could be considered an instantaneous decision. Once made, it leaves the table, and everything inside the block is evaluated without any further consideration of the initial condition. If changes are made, the entire ruleset gets re-evaluated, and the new @if check has the same instantaneous property.

@when, on the other hand, implies an ongoing decision, which has two implications: First, that the condition might regularly change state (as you imply in your response there), and second, where everything within the block is evaluated while keeping that ongoing decision in mind.

However, most of the context of that differentiation comes from my idea that namespaces can be implemented almost entirely with existing tools, without needing to introduce a ton of extra semantics. However I did realize by the end of it that it would require the @when condition be 'maintained' as a validity check for every selector within the conditional block (for conditionals which cannot be evaluated outside the context of a selector; namely, custom properties), and that's a subtle enough factor that it could impact the usability of the idea.

I strongly prefer @if, @else, and @else-if. It would be clear and obvious for most authors, and SASS could easily adapt.

Level 5 at this point.

@upsuper https://github.com/w3c/csswg-drafts/issues/112#issuecomment-219196404

As a web developer, I'd prefer not putting it inside, which looks weird, and doesn't match the intuition from any other programming language.

As a browser developer, though, I'm concerned about how CSSOM should handle this.

Also I guess it makes sense to allow @else to precede another @media or @support, like a if-elseif structure. But that would likely make story of CSSOM more complicated.

Well, an else-if statement is just:

if (a) {

} else {
  if (b) {

  }
}

Another option if we want to avoid the awkwardness of where the @else or @not would go is to have an at rule that contains all of the conditions to check. Strawperson:

@switch media {
  (min-width: 800px) { /* rules */ }
  (min-width: 600px) { /* rules */ }
  default { /* rules */ }
}

@switch supports {
  (color: rainbow) { /* rules */ }
  default { /* rules */ }
}

Not sure how well that works for backwards compat.

(And maybe you'd want to just have a single @switch with both MQs and supports conditions inside it.)

It feels like @heycam's suggestion would also be gentler on the OM than an @if {} @else {}
Converting my earlier example:

@if media((width >= 400px) and (pointer: fine)) and supports(display: flex) {
   ...
}
@else-if supports((caret-color: pink) or (background: double-rainbow()){
  ...
}
@else {
  ...
}

becomes

@switch {
  media((width >= 400px) and (pointer: fine)) and supports(display: flex) {
     ...
  }
  supports((caret-color: pink) or (background: double-rainbow()){
    ...
  }
  default {
    ...
  }
}

This seems quite workable to me.

The CSS Working Group just discussed else conditional.

The full IRC log of that discussion
<astearns> topic: else conditional

<jensimmons> people aren’t really using the media query. they will use thr property a lot

<astearns> github: https://github.com/w3c/csswg-drafts/issues/112

<fremy> TabAtkins: heycam wanted to put this on the agenda, and I'd be interested to know why he brought this up

<fremy> heycam: I made a proposal and it never got discussed further

<TabAtkins> http://tabatkins.github.io/specs/css-when-else/

<fremy> TabAtkins: yeah, I had limited time so I didn't push forward, but if there is interest I can reprioritize my work

<fremy> astearns: so, heycam, are you volounteering to implement that?

<fremy> heycam: no

<fremy> TabAtkins: in that case, I'd rather leave this in the status of proposal

<fremy> fantasai: could we maybe cross-link the issue in the draft, so this discussion is available to readers?

<fremy> TabAtkins: okay, sounds reasonable

<fremy> TabAtkins: it's not right now, but I can do this

<fremy> heycam: so, shall we table this discussion for now?

<fremy> florian: just wanted to note that also unless everybody is doing it, it's not useful

<fremy> myles_: sure, but if one does it and authors finds it useful, others will follow

<fremy> TabAtkins: but the point is that right now we don't even have one promise to implement

<fremy> astearns: okay, let's try to do easing timing functions, then take a break

The reason I didn't like @switch equivalents is that it still only lets you test on a single type of conditional at a time. If you want to test for a MQ and an SQ, or else do something else, you can't write that reasonably in @switch.

That is:

@switch {
  @media A {
    @supports B {
      /* code intended for (A && B) */
    }
  }
  @media not A {
    /* code intended for (!A) 
       aka (!A && B) or (!A && !B) */
  }
  @default {
    /* code intended for (A && !B),
       the leftover case */
  }
}

will never run the @default block (because the two top-level rules fully cover A and not A), even if the intention is to have it contain code for when A is true but B is false.

Versus when/else, which would handle it well:

@when media(A) and supports(B) {
  /* (A && B) */
}
@else media(not A) {
  /* (!A), which is (!A && B) || (!A && !B) */
}
@else {
  /* (A && !B) */
}

I don't think it's possible to mod @switch into doing this, because the top-level rules aren't constrained to contain only a single child conditional; it could have several, so you can't easily tell what cases it's supposed to cover (and thus what "default" should cover).

@when is familiar to those who know SQL which has WHEN in both variants of its CASE expression and no IF.

Was this page helpful?
0 / 5 - 0 ratings