[csswg-drafts] [css-color-5] `color-scale()` for interpolating across multiple colors (#10034)

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

== [css-color-5] `color-scale()` for interpolating across multiple colors ==
During the discussion in #9992 it occurred to me that one of the things that could really help simplify the color-related design systems use cases would be a way to define a gradient line and pick a color on it.

## Why?

- As a primary use case, authors often need to define scales of colors with interim colors inserted to adjust the interpolation, and `color-mix()` is not very friendly to that. Super common example: the good-bad scale of red, orange, yellow, green.
- Design systems could then define color scales and pass them around in variables, which would make functions a much more appealing solution for actually selecting points on those scales.
- It makes interpolation with arbitrary manual interim points much easier, without requiring special overfit syntax in variable groups to cater to this.
- The scale specification should be compatible with gradient color stops so that authors can debug them by simply throwing them into a `linear-gradient()`.

## Syntax

### Option 1: Single function for both *defining* the scale, and _selecting_ a color

```
<color-scale()> = color-scale ( <percentage> / <color-interpolation-method>?, <abstract-color-stop-list> )
<abstract-color-stop-list> =   <abstract-color-stop> , [ <abstract-color-hint>? , <abstract-color-stop> ]#
<abstract-color-stop> = <color> <percentage>?
```

Example usage:

```css
--tints-green: white, var(--color-green), black;
--color-green-900: color-scale(90% / var(--tint-green));
```

This is basically modeled after `linear-gradient()` with the non relevant parts removed (lengths, `to <whatever>`, angles).
It could also allow `<1d-image>` / `stripes()` to facilitate discrete scales.

The reason the percentage is separated from the rest with a `/` is to facilitate storing the actual scale part into variables and passing them around without having to worry about whether you need to specify a comma or not (depending on whether the scale has a `<color-interpolation-method>`).

Pros:
- By passing a list of arguments around, these can produce *both* a color scale *and* various types of gradients (without gradients having to be extended in any way)
- Color stop list could even be extended by adding more stops on either side
Cons:
- Scale variables don't make sense by themselves, as they're just a comma-separated list of colors.

### Option 2: Separate scale definition and color selection

This syntax decouples the scale from the color selection.
It seems more conceptually sound, but also too many parens.

```
<color-scale> = color-scale ( <color-interpolation-method>?, <abstract-color-stop-list> )
<abstract-color-stop-list> =   <abstract-color-stop> , [ <abstract-color-hint>? , <abstract-color-stop> ]#
<abstract-color-stop> = <color> <percentage>?

<color-pick()> = color-pick(<percentage> of <color-scale>);
```

Example:

```css
--tints-green: color-scale(white, var(--color-green), black);
--color-green-900: color-pick(90% of var(--tints-green));
```

the parens could be reduced if it would be possible to define tokens like:

```
<color-scale-color> = <percentage> of <color-scale>
```

Example:
```css
--tints-green: color-scale(white, var(--color-green), black);
--color-green-900: 90% of var(--tints-green);
```

but I suspect @tabatkins will have a good reason to rule that out 😁 

We could also make it a variant of `color-mix()`:

```
<color-mix> := color-mix(<percentage> of <color-scale>)
```

Example:
```css
--tints-green: color-scale(white, var(--color-green), black);
--color-green-900: color-mix(90% of var(--tints-green));
```

Though since conceptually we're not mixing anything, I don't think this is worth it.

## More Examples

### Transparent variations of a base color

Option 1:
```css
--color-neutral-a: var(--color-neutral), transparent;
--color-neutral-a-90: color-scale(90% / var(--color-neutral-a));
```

Option 2:
```css
--color-neutral-a: color-scale(var(--color-neutral), transparent);
--color-neutral-a-90: 90% of var(--color-neutral-a));
```

### Success/failure scales 

This is super common to communicate varying levels of success/failure.
There are two main forms: red - orange - yellow -  green - dark green, or red - white - green.
E.g. see screenshot from Coda’s conditional formatting: 

<img width="329" alt="image" src="https://github.com/w3c/csswg-drafts/assets/175836/9215ca1c-89e2-4d9a-aa2e-04de367afc91">

Especially the red - orange - yellow - green scales almost always require manual correction, and cannot be done with a single 2 color interpolation (yes not even in polar spaces).
With `color-scale()` they can be as simple as:

```css
:root {
 --color-scale-bad-good: in oklch, var(--red), var(--orange), var(--yellow) 50%, var(--green) 90%, var(--dark-green);
}

.badge {
 background: color-scale(var(--percent-good) of var(--color-scale-bad-good));

 .great { --percent-good: 100% }
 .good { --percent-good: 80% }
 .ok { --percent-good: 60% }
 .fair { --percent-good: 40% } 
 .poor { --percent-good: 20% } 
 .terrible { --percent-good: 0% } 
}
```

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


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

Received on Wednesday, 6 March 2024 03:34:31 UTC