Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support CSS Color Level 4 hwb/lab/lch/gray functions (and related adjustments) #2831

Open
10 of 13 tasks
mirisuzanne opened this issue Mar 5, 2020 · 24 comments
Open
10 of 13 tasks
Labels

Comments

@mirisuzanne
Copy link
Contributor

mirisuzanne commented Mar 5, 2020


See Color Level 4

Having access to CIE colors/adjustments would be particularly useful for design systems, and it seems like we could provide the syntax in sass:color without waiting on browsers to support the CSS variant.

When I say adjustments, I'm not referring to the new CSS color functions in Level 5. I think we need to wait for those to solidify. I'm thinking of something similar to, or extending, color.adjust().

@nex3 nex3 added enhancement New feature or request planned We would like to add this feature at some point labels Mar 5, 2020
@nex3
Copy link
Contributor

nex3 commented Mar 5, 2020

I'd definitely like to expand the set of built-in color functions, like we did for math functions in #851. Miriam, are you interested in writing up a proposal?

@mirisuzanne
Copy link
Contributor Author

Yeah, I'm happy to. I see a few potential issues right away, and I'd love your thoughts:

Currently, color-adjust provides parameters for individually adjusting RGB or HSL values (both in an sRGB color space). LAB & LCH have values like lightness and hue that use the same name, even a similar syntax, but a different meaning in CIE color space. That's not a problem for the lch() and lab() functions, but I'm not sure how to address it with e.g. color.adjust(lch(…), $hue: …).

The level 5 spec provides a clever solution with the relative colors syntax, defining adjustments inside the respective color functions (hsl(from var(--accent) calc(h+180) s l) but I'm not sure if it's safe to rely on a proposed syntax like that.

Also: since no browsers support the new color formats, we're likely converting everything to sRGB for output. Since sRGB and CIE provide different color gamuts, that may not be a good long term solution, once CIE is supported in browsers. Do we need to plan ahead for that change in some way?

@nex3
Copy link
Contributor

nex3 commented Mar 5, 2020

Good questions! I think my first high-level response is another question: what's the benefit of providing LAB- and LCH-friendly functions if we can't accurately represent their color spaces? Does it even make sense to start adding those when we know we'll probably want to support them differently in the future?

@mirisuzanne
Copy link
Contributor Author

The main advantage is that values & adjustments in the CIE color space are perceptually uniform. So blue and yellow hues at the same chroma & lightness (in LCH) will look similarly bright to the eye, and provide similar contrast ratios against a background color. That's a big advantage for doing any sort of automated color management… assuming you stay in the rgb gamut.

I think that conversion will likely still happen for most users even once browsers support CIE formats, because most monitors use sRGB for display. If that's true, the conversion would only become an issue for people designing for wide-gamut displays…? I could do a bit more research…

@mirisuzanne
Copy link
Contributor Author

ok, I have confirmation from Tab that it's a reasonable idea to do some conversion:

Most monitors can only display approximately the sRGB gamut (or less!) anyway, so lab() is just a fun new way to write rgb() (the coordinates have better behavior, that's all).
And for higher-def monitors, only the out-of-sRGB colors will be affected, and you can warn about that.
(And provide strategies for how to squish them into sRGB; there are several possibilities with different trade-offs.)
(We're discussing high-end color support in Chrome right now, and it looks like we'll be eagerly converting everything into the extended-sRGB colorspace to work with anyway, since that's what Skia uses.)
https://drafts.csswg.org/css-color-4/#descdef-color-profile-rendering-intent for more details
(linking over to some papers for the actual more details)
Since people probably expect their in-gamut colors to stay as specified, you're gonna be doing some variety of relative-colorimetric fixing.
But lots of variety there still.

There's a lot of detail in the spec, including sample conversion functions.

@nex3
Copy link
Contributor

nex3 commented Mar 11, 2020

I'm not sure how safe it is for us to rely on "most monitors". Sass is generally pretty conservative with its polyfilling, for a couple reasons:

  • We want the same Sass file to behave the same way everywhere, so we avoid configuration that affects the way the language works. This means that the whole language has to work for everyone—including people working with wide-gamut monitors, people targeting non-screen media, and so on.

  • The status quo is always changing. Back in the day, Sass polyfilling hsl() was a feature—now, people are clamoring for us not to compile it out because they (mistakenly) believe that it has a wider gamut than RGB colors. Any behavior we add now will be very painful to change later, even if five or ten years down the line wide-gamut monitors and native browser support for lab() are the norm.

These factors make me want to avoid eagerly or silently converting colors to RGB. I think you're probably right that it's useful to do transformations and definitions in these color spaces, but we need to find a way to do that that will continue to make sense if wide-gamut colors become widespread.

@mirisuzanne
Copy link
Contributor Author

Yeah, I think we should not be adding these to the global namespace. But if we put them in the sass:color module, they can provide both consistent sRGB output, and a straight-forward optional upgrade path. Authors can start using the plain CSS functions on their own schedule, without our intervention, and the module functions will continue to be useful for supporting old browsers.

I'm thinking of them like the sass:math functions that overlap CSS – a bit more limited than what the eventual CSS functions will provide, but a consistent and backwards-compatible pre-compiled option. No config required, and no more per-screen variation than using hsl() or rgb() now.

While there are several methods for handling out-of-gamut colors, it's clear (and defined in the CSS spec) that we should be using "relative-colorimetric" rendering. That doesn't leave much that we need to decide. At this point, I think if we can find a good method for calculating relative-colorimetric adjustments, I can finish putting together a proposal.

@mirisuzanne
Copy link
Contributor Author

mirisuzanne commented Mar 12, 2020

hwb() is an sRGB format already, so the conversion would be lossless. But we might want to namespace it anyway, to avoid complaints similar to the current hsl()-conversion…

@mirisuzanne
Copy link
Contributor Author

I'm told by the editors of CSS Color level 4 that the best solution is to lock hue & lightness, then reduce chroma until the color is in-gamut.

I've created a (rough, slow) demo of that approach.

@mirisuzanne
Copy link
Contributor Author

Ok, colors are complex. I think we can break this down a bit more, and take it a step at a time

  1. hwb() is part of the sRGB color space we use for other functions. We could implement it with minimal considerations. I'm going to break this out into a stand-alone proposal while we discuss the other features.

  2. CSS is planning to use lch() as the default space for color-mixing and adjustment functions. It is not gamut-limited, and it is perceptually uniform, so it makes a lot of sense as the default. I'm not sure we can change our adjustment defaults at this point, but we should be working towards full support of Lab/LCH colors asap – and there's a lot we can provide without getting in the way of forwards-compatibility. I'm feeling good about my progress on this proposal so far, but it involves some big questions about how we want to handle multiple colorspaces in adjust/scale/change/mix as well as lightness/hue functions.

  3. Going further, CSS is moving towards full support of ICC color profiles. Some are predefined (srgb, display-p3, a98-rgb, prophoto-rgb, and rec-2020), but new profiles can be attached with the @color-profile rule. The color() function is proposed to set colors using any profile. I think that could be useful for us to support, and is related to this issue, but will take more significant research. I'd like to create a new issue for that, and handle it later.

  4. The "relative colors" proposal in level 5 is also very interesting. Allows adjustments inside color functions, where the space is already implicit: hsl(from <color> calc(h+180) s l). But that proposal is more invasive, and still in flux. We probably want to hold off a bit.

@nex3
Copy link
Contributor

nex3 commented Apr 15, 2020

Sorry for taking so long to come back to this.

Yeah, I think we should not be adding these to the global namespace. But if we put them in the sass:color module, they can provide both consistent sRGB output, and a straight-forward optional upgrade path. Authors can start using the plain CSS functions on their own schedule, without our intervention, and the module functions will continue to be useful for supporting old browsers.

I'm thinking of them like the sass:math functions that overlap CSS – a bit more limited than what the eventual CSS functions will provide, but a consistent and backwards-compatible pre-compiled option. No config required, and no more per-screen variation than using hsl() or rgb() now.

While there are several methods for handling out-of-gamut colors, it's clear (and defined in the CSS spec) that we should be using "relative-colorimetric" rendering. That doesn't leave much that we need to decide. At this point, I think if we can find a good method for calculating relative-colorimetric adjustments, I can finish putting together a proposal.

Cordoning functions in a module is beneficial because it makes it less likely that we'll need to migrate away from those functions, but it doesn't really make it less painful if we do end up needing to migrate. I want to make sure (as much as possible) that any functions we add are future-proof in and of themselves, even if there's a reasonable migration path.

I'm not saying there's no way it makes sense to fake wider gamuts in the short term, but whatever function names and syntax we come up with for doing so need to be able to do the same thing, and still be comprehensible, in a hypothetical future world where 99% of monitors and browsers support wide-gamut colors. If we end up in that world and Sass has a color.lab() function that returns an sRGB color, that's going to be very unexpected for users.

One possibility would be to define a function like color.lab($lightness, $a, $b, $alpha, $to), where the $to argument specifies a target colorspace. Initially users would be required to specify $to: srgb, but once we support native LAB colors that could be relaxed.

Ok, colors are complex. I think we can break this down a bit more, and take it a step at a time

  1. hwb() is part of the sRGB color space we use for other functions. We could implement it with minimal considerations. I'm going to break this out into a stand-alone proposal while we discuss the other features.

👍

  1. CSS is planning to use lch() as the default space for color-mixing and adjustment functions. It is not gamut-limited, and it is perceptually uniform, so it makes a lot of sense as the default. I'm not sure we can change our adjustment defaults at this point, but we should be working towards full support of Lab/LCH colors asap – and there's a lot we can provide without getting in the way of forwards-compatibility. I'm feeling good about my progress on this proposal so far, but it involves some big questions about how we want to handle multiple colorspaces in adjust/scale/change/mix as well as lightness/hue functions.

Looking forward to reading your proposals!

  1. Going further, CSS is moving towards full support of ICC color profiles. Some are predefined (srgb, display-p3, a98-rgb, prophoto-rgb, and rec-2020), but new profiles can be attached with the @color-profile rule. The color() function is proposed to set colors using any profile. I think that could be useful for us to support, and is related to this issue, but will take more significant research. I'd like to create a new issue for that, and handle it later.

My guess is that this is something we'll have to punt to render-time for the most part—it's unlikely that we'll be able to accurately resolve at least URLs in @color-profile rules, and possibly other declarations as well.

  1. The "relative colors" proposal in level 5 is also very interesting. Allows adjustments inside color functions, where the space is already implicit: hsl(from <color> calc(h+180) s l). But that proposal is more invasive, and still in flux. We probably want to hold off a bit.

Yeah, it would be cool to support this in Sass but also a LOT of work.

@mirisuzanne
Copy link
Contributor Author

I'm not saying there's no way it makes sense to fake wider gamuts in the short term, but whatever function names and syntax we come up with for doing so need to be able to do the same thing, and still be comprehensible, in a hypothetical future world where 99% of monitors and browsers support wide-gamut colors. If we end up in that world and Sass has a color.lab() function that returns an sRGB color, that's going to be very unexpected for users.

One possibility would be to define a function like color.lab($lightness, $a, $b, $alpha, $to), where the $to argument specifies a target colorspace. Initially users would be required to specify $to: srgb, but once we support native LAB colors that could be relaxed.

This approach makes a lot of sense to me. Maybe $to could accept any supported format (rgb, hsl, etc), rather than a generic color-space? That would avoid some confusion about what's a format vs a color-space, while allowing authors to control output in a way they are currently unable to.

  1. hwb() is part of the sRGB color space we use for other functions. We could implement it with minimal considerations. I'm going to break this out into a stand-alone proposal while we discuss the other features.

👍

#2835 is ready for review, unless we want to consider adjusting it based on the broader discussion: like adding a $to: <format> option, even within sRGB formats.

I wonder if a solution like this could extend to existing formats like hsl(), allowing them to also specify a desired output. That would need some form of auto-like default for current behavior. Too far, or worth pursuing?

@nex3
Copy link
Contributor

nex3 commented Apr 16, 2020

This approach makes a lot of sense to me. Maybe $to could accept any supported format (rgb, hsl, etc), rather than a generic color-space? That would avoid some confusion about what's a format vs a color-space, while allowing authors to control output in a way they are currently unable to.

I don't like the idea of further muddying the distinction between color functions and color spaces. In my experience, conflating two similar ideas in the name of simplicity causes more confusion than being consistently clear about which type of name goes where.

#2835 is ready for review, unless we want to consider adjusting it based on the broader discussion: like adding a $to: <format> option, even within sRGB formats.

Reviewed!

I wonder if a solution like this could extend to existing formats like hsl(), allowing them to also specify a desired output. That would need some form of auto-like default for current behavior. Too far, or worth pursuing?

I really don't want to make color output formats super customizable. It adds overhead to the internal color representation and its behavior in compressed mode is either going to be frustrating to some people because it prefers the chosen format rather than the smallest format, or frustrating to others because it makes the opposite decision.

@chriseppstein
Copy link

It's also worth noting that the rgb and hsl functions now accept a non comma delimited form that uses a / as a delimiter for the alpha channel. Also they allow decimals now. E.g. rbg(187.25 0 255 / 0.5).

@glen-84
Copy link

glen-84 commented Jun 19, 2020

Should a separate issue be opened for the syntax mentioned above?

The syntax is recommended by Mathias Bynens, but it's not possible to use it with Sass:

wrong number of arguments (1 for 3) for `rgb'

https://codepen.io/glen-84/pen/qBbRXXW

@nex3
Copy link
Contributor

nex3 commented Jun 19, 2020

@glen-84 That syntax has been supported in Dart Sass since version 1.15.0. See the Sass documentation for details.

@glen-84
Copy link

glen-84 commented Jun 20, 2020

@nex3,

Apologies, I have found #2564 and sass/libsass#2722.

I guess CodePen is using LibSass. This is also true for some of my own projects.

Thanks.

@michaelurban
Copy link

Any movement on this? It would make implementing Material 3's tonal color palettes a breeze.

@nex3
Copy link
Contributor

nex3 commented Nov 24, 2021

@mirisuzanne is planning to start working on a design here in December.

@michaelurban
Copy link

@nex3, @mirisuzanne Always nice when either of you two pop up in an issue. Usually means things are going to work eventually.

@mirisuzanne I'm currently using blend to get pretttttty close to Material 3's tonal palettes, thanks!

in-browser-over-web

nex3 added a commit to sass/sass-site that referenced this issue Aug 1, 2023
nex3 added a commit to sass/sass-site that referenced this issue Aug 1, 2023
nex3 added a commit to sass/dart-sass that referenced this issue Aug 1, 2023
nazarepiedady pushed a commit to nazarepiedady/sass-site that referenced this issue Aug 11, 2023
nex3 added a commit to sass/dart-sass that referenced this issue Aug 22, 2023
nex3 added a commit that referenced this issue Apr 10, 2024
Although these are still in flux (largely due to CSS changing under
our feet to some degree), it's been almost two years since we put them
out for review initially so I think it's fair to say that the core
principles are solid.

See #2831
nex3 added a commit that referenced this issue Apr 11, 2024
Although these are still in flux (largely due to CSS changing under
our feet to some degree), it's been almost two years since we put them
out for review initially so I think it's fair to say that the core
principles are solid.

See #2831
asaf400 pushed a commit to asaf400/ass-site that referenced this issue Apr 18, 2024
asaf400 pushed a commit to asaf400/ass-site that referenced this issue Apr 18, 2024
asaf400 pushed a commit to asaf400/ass-site that referenced this issue Apr 18, 2024
asaf400 pushed a commit to asaf400/ass-site that referenced this issue Apr 18, 2024
aduth added a commit to 18F/identity-idp that referenced this issue May 7, 2024
Due to Sass incompatibilities

See: sass/sass#2831
aduth added a commit to 18F/identity-idp that referenced this issue May 7, 2024
Due to Sass incompatibilities

See: sass/sass#2831
aduth added a commit to 18F/identity-idp that referenced this issue May 7, 2024
…ard (#10564)

* Replace stylelint-config-recommended-scss with stylelint-config-standard-scss

changelog: Internal, Build Tools, Replace stylelint-config-recommended-scss with stylelint-config-standard-scss

* Disable color-function-notation

Due to Sass incompatibilities

See: sass/sass#2831

* Fix lint errors

* Bump version to 5.0.0-beta.1

* Move reportNeedlessDisables into shared configuration
@TheAtomicOption
Copy link

TheAtomicOption commented Sep 18, 2024

Not sure if this should be its own issue, but seems related given the timing and error message:

1.79.1 broke my team's SASS builds which are compiled through dartsass (via a .dart script, not the js port). The compilation runs fine on 1.78.0, and the error is related to color spaces.

Our pubspec.yaml version string for sass was ^1.75.0, and the resulting error was of the form:

Unhandled exception: Error: $description: Unknown color space "main-color"
131 │             color: color(main-color);

Is what I'm seeing here likely a bug with 1.79.1 release or was there an intended breaking change? (in the second case there should be a major version bump instead of a minor one per semantic versioning, right?)

@nex3
Copy link
Contributor

nex3 commented Sep 19, 2024

color(main-color) is invalid CSS. The syntax of the CSS color() function as defined in CSS Color 4 is

color() = color( <colorspace-params> [ / [ <alpha-value> | none ] ]? )
<colorspace-params> = [ <predefined-rgb-params> | <xyz-params>]
<predefined-rgb-params> = <predefined-rgb> [ <number> | <percentage> | none ]{3}
<predefined-rgb> = srgb | srgb-linear | display-p3 | a98-rgb | prophoto-rgb | rec2020
<xyz-params> = <xyz-space> [ <number> | <percentage> | none ]{3}
<xyz-space> = xyz | xyz-d50 | xyz-d65

Per Sass's backwards compatibility policy, we don't consider changes to stylesheets that already produced invalid CSS to be breaking changes that need a major semantic versioning release.

If you're parsing the syntax color(main-color) in some other way, I recommend changing the function name to something that's got a namespace so you're not at risk of conflicting with changes to CSS. In the meantime, you can work around this by adding:

@function color(...$args) {
  @return #{color}(...$args);
}

(Example)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants