[csswg-drafts] [css-conditional] `fallback()` function (#7881)

brandonmcconnell has just created a new issue for https://github.com/w3c/csswg-drafts:

== [css-conditional] `fallback()` function ==
# The problem
I just recently became more increasingly aware of how CSS doesn't provide a built-in method for providing fallback values, outside of what's currently supported within [`var()`](https://w3c.github.io/csswg-drafts/css-variables-1/#using-variables) and what's coming to [`attr()`](https://github.com/web-platform-tests/interop/issues/86).

With this in mind, if I wanted to provide multiple fallback values for an expression based on the first that works, the general approaches are these—

1. **Declare the property multiple times, in priority order (ascending):**

   ```css
   element {
     color: black;
     color: currentcolor;
     color: color(display-p3 1 0 0.87);
   }
   ```
   
2. **Custom properties**
   If, however, you want to bring custom properties into the mix, it suddenly becomes a bit more complicated, as `var()` always validates as truthy, so you instead need to rely on `var`'s built-in fallback value.
   
   ```css
   element {
     color: var(--property-1, var(--property-2, var(--property-3, black)));
   }
   ```
  
As is clear in the second example above, nesting in this way can get pretty messy, not to mention that this method doesn't support multiple fallback values taking into current account browser support.

With the same type of nesting and incorporating the [`if()`](https://github.com/w3c/csswg-drafts/issues/4731) function currently being discussed, it might look something more like this:

```css
element {
  color: var(--property-1, var(--property-2, var(--property-3, if(supports(background: color(display-p3 1 0 0.87)), color(display-p3 1 0 0.87), black))));
}
```

This same pattern could be used to implement colors in color spaces many browsers don't support, and that's still just one example using color for the sake of this example.

And still… this example is awfully messy.

# Description of the proposal
I propose adding a `fallback()` (or something similar; I'm not sold on the name), which takes a list of values and returns the first value that doesn't error out (or that doesn't return an undefined value).

### Syntax

```css
fallback(value1 [, value2?, value3?, ..., valueN?])
```

### Usage

```css
element {
  color: fallback(--property-1, --property-2, --property-3, color(display-p3 1 0 0.87), black);
}
```

It's important to note, as my previous examples might be found misleading, that this would **_not_** change the existing functionality of `var()`. Take for example the _same_ example from above, but wrapping each custom property name with `var()`:

```css
element {
  color: fallback(var(--property-1), var(--property-2), var(--property-3), color(display-p3 1 0 0.87), black);
}
```

Because `var()` always validates as truthy, the above example would always evaluate to `var(--property-1)` even if its value is undefined. This is important so as, not to clash with the existing functionality of `var()`. This is still helpful, as variables can still be used with their own fallback values if the variable value is undefined.

As such, these two would be equivalent ✅

```css
element {
  color: fallback(--property, color(display-p3 1 0 0.87), black);
}
element {
  color: fallback(var(--property, color(display-p3 1 0 0.87)), black);
}
```

These two are not ❌

```css
element {
  color: fallback(var(--property, color(display-p3 1 0 0.87)), black);
}
element {
  color: fallback(var(--property), color(display-p3 1 0 0.87), black);
                /* └→ always evaluates to var(--property-1) */
}
```

So generally, speaking, this:

```css
element {
  color: fallback(--property, color(display-p3 1 0 0.87), black); /* ✅ */
}
```

…would be preferred over this:

```css
element {
  color: fallback(var(--property, color(display-p3 1 0 0.87)), black); /* 🤷🏻‍♂️ */
}
```

…though I can certainly foresee IRL examples where the latter would be what is _evaluated_ (but not actually _used_) simply because one variable value used inside `fallback()` has its own fallback value already.

For example:

```css
parent {
  --default: color(display-p3 1 0 0.87);
  --property: var(--default, color(display-p3 0 1 0.87));
  element {
    color: fallback(--property, black);
  }
}
```

This would be perfectly acceptable and would be the equivalent of this:

```css
parent {
  element {
    color: fallback(color(display-p3 1 0 0.87), color(display-p3 0 1 0.87), black);
  } /*                ↑                           ↑                           ↑
                 --default                   --property                     black    */
}
```

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/7881 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Thursday, 13 October 2022 21:09:18 UTC