Re: [csswg-drafts] [css-values]: Express conditional values in a more terse way (#5009)

One of the major objections during the call was the idea that we should just rely on nested MQs going forward, as that's already something we plan on doing anyway.

I don't think this is a good idea. We *do* need to be able to nest MQs inside of style blocks (for the same reason presented here, in fact!), for when adjusting to a MQ involves touching multiple properties at once. Trying to coordinate that by using a `switch()` in each property independently would be both hard to write and hard to read, bad idea in general.

But when one is just adjusting a single property according to an MQ (relevant to us here: adjusting colors for the combo of {light, dark}x{high-contrast, low-contrast}), using MQs gets real verbose real fast, *even if* we pretend that custom MQs or custom at-rules are already a thing.

Here's a real-world example, taken from the W3C stylesheet. It's a bit long, so I can show off the effect of multiple color properties being affected:

```css
 .note {
  border-color: #52E052;
  background: #E9FBE9;
  overflow: auto;
 }

 .note::before, .note > .marker,
 details.note > summary::before,
 details.note > summary > .marker {
  text-transform: uppercase;
  display: block;
  color: hsl(120, 70%, 30%);
 }
 /* Add .note::before { content: "Note"; } for autogen label,
    or use class="marker" to mark up the label in source. */

 details.note > summary {
  display: block;
  color: hsl(120, 70%, 30%);
 }
 details.note[open] > summary {
  border-bottom: 1px silver solid;
 }
```

Here's the same code, using MQs and switch() to adjust colors for the aforementioned four combinations. (To avoid me having to actually do design work for the purpose of an example, I'm just using four arbitrary colors in every case.)

```css
@media not (prefers-color-scheme: dark) and not (prefers-contrast: low) {
  :root { --c: 1; } // light, high-contrast
}
@media not (prefers-color-scheme: dark) and (prefers-contrast: low) {
  :root { --c: 2; } // light, low-contrast
}
@media (prefers-color-scheme: dark) and  not (prefers-contrast: low) {
  :root { --c: 3; } // dark, high-contrast
}
@media (prefers-color-scheme: dark) and (prefers-contrast: low) {
  :root { --c: 4; } // dark, low-contrast
}

 .note {
  border-color: switch(var(--c); #52E052; #52E052; #52E052; #52E052);
  background: switch(var(--c); #52E052; #52E052; #52E052; #52E052);
  overflow: auto;
 }

 .note::before, .note > .marker,
 details.note > summary::before,
 details.note > summary > .marker {
  text-transform: uppercase;
  display: block;
  color: switch(var(--c); #52E052; #52E052; #52E052; #52E052);
 }
 /* Add .note::before { content: "Note"; } for autogen label,
    or use class="marker" to mark up the label in source. */

 details.note > summary {
  display: block;
  color: switch(var(--c); hsl(120, 70%, 30%); hsl(120, 70%, 30%); hsl(120, 70%, 30%); hsl(120, 70%, 30%));
 }
 details.note[open] > summary {
  border-bottom: 1px switch(var(--c); hsl(120, 70%, 30%); hsl(120, 70%, 30%); hsl(120, 70%, 30%); hsl(120, 70%, 30%)) solid;
 }
```

And now here's the same code using nested MQs. To help it out, I'm going to go ahead and assume that MQ aliases exist, because otherwise it's just ridiculously verbose. (And similarly, I'm just spamming colors rather than do design work to actually do representative colors. ^_^)

```css
@custom-media --light-high not (prefers-color-scheme: dark) and not (prefers-contrast: low);
@custom-media --light-low not (prefers-color-scheme: dark) and (prefers-contrast: low);
@custom-media --dark-high (prefers-color-scheme: dark) and  not (prefers-contrast: low);
@custom-media --dark-low (prefers-color-scheme: dark) and (prefers-contrast: low);

 .note {
  @media (--light-high) {
    border-color: #52E052;
    background: #E9FBE9;
        } @media (--light-low) {
    border-color: #52E052;
    background: #E9FBE9;
        } @media (--dark-high) {
    border-color: #52E052;
    background: #E9FBE9;
        } @media (--dark-low) {
    border-color: #52E052;
    background: #E9FBE9;
        }
  overflow: auto;
 }

 .note::before, .note > .marker,
 details.note > summary::before,
 details.note > summary > .marker {
  text-transform: uppercase;
  display: block;
  @media (--light-high) {
    color: hsl(120, 70%, 30%);
        } @media (--light-low) {
    color: hsl(120, 70%, 30%);
        } @media (--dark-high) {
    color: hsl(120, 70%, 30%);
        } @media (--dark-low) {
    color: hsl(120, 70%, 30%);
        }
 }
 /* Add .note::before { content: "Note"; } for autogen label,
    or use class="marker" to mark up the label in source. */

 details.note > summary {
  display: block;
  @media (--light-high) {
    color: hsl(120, 70%, 30%);
        } @media (--light-low) {
    color: hsl(120, 70%, 30%);
        } @media (--dark-high) {
    color: hsl(120, 70%, 30%);
        } @media (--dark-low) {
    color: hsl(120, 70%, 30%);
        }
  color: hsl(120, 70%, 30%);
 }
 details.note[open] > summary {
  border-bottom: 1px silver solid;
  @media (--light-high) {
    border-color: #AE1E1E;
        } @media (--light-low) {
    border-color: #AE1E1E;
        } @media (--dark-high) {
    border-color: #AE1E1E;
        } @media (--dark-low) {
    border-color: #AE1E1E;
        }
 }
```

I think the results are pretty clear here - even *with* the significant verbosity savings from a `@custom-media`, you're still turning every color-using property into roughly 8x as many lines, with tons of repetition across those (both repetition of the `@media` lines, and the property names; I also slightly rejiggered the `border` declaration in the final rule to avoid repeating the non-color values). The whole section goes from "easily fits on my screen" to "more than a screenful of content", with what is imo a lot of visual noise.

Contrast with the switch() example, where the color properties get longer (by a little more than 4x), but you don't gain any lines.  (Longer starting declarations would probably encourage the author to linebreak between values, so you would increase the number of lines, but only by 4x, or maybe 5x if you keep the `switch(var(--c),` on the first line and put the values on subsequent ones.)  There's also a bare minimum of repetition; the only repeated bit is `switch(var(--c),` in each value, because the logic behind the switching was centralized in the MQs at the start of the document and doesn't need to be repeated at each usage site.

And this was in just one small chunk of the W3C stylesheet - there are a lot more colors across the whole sheet than just these four instances. I think this example is both very realistic *and* fairly minimal; in real-world stylesheets I think there are often even more conditions than this.

-- 
GitHub Notification of comment by tabatkins
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/5009#issuecomment-625562169 using your GitHub account

Received on Friday, 8 May 2020 00:21:24 UTC