-
Notifications
You must be signed in to change notification settings - Fork 711
[css-color-6] How to support color math involving more than one color? #11533
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
Comments
Adding symbols into the component name might cause confusion, e.g. |
@tiaanl You need spaces around operators in math functions, so it woudl be @LeaVerou I agree something like this is useful. It's simply impossible to mix two colors in a mathematical way right now, which does indeed block several good use-cases. I'm not the happiest about the readability of just listing several colors before the components, but I can't come up with a better syntax given the syntax we're starting from. I think it's probably the best we can do. I definitely prefer just using c2/etc for the naming. No need to get complicated here; that syntax space should be safe to reserve for colors. Another possibility, which I'm not super happy with, is to revive your older idea of a component-extractor function. That way, you can pull out the components you need from the second color into variables, then use them in RCS normally. It's more verbose, but even more low-level. In theory, it would also mean you could mix components from different color spaces, but I doubt that would ever be a meaningful operation. |
The CSS Working Group just discussed
The full IRC log of that discussion<kbabbitt> lea: a while back we resolved to adopt relative color syntax<kbabbitt> ... <kbabbitt> ... it's now in every browser, allows for math based on components of colors <kbabbitt> ... use cases that involve more than one color keep cropping up <hober> q+ <kbabbitt> ... e.g. mixing certain components from one color and other compionents from another <kbabbitt> ... had an internal use case using components from one color and alpha from another <kbabbitt> ... polyfilling other features can require components from multiple colors <kbabbitt> ... e.g. two color operations like blending modes <kbabbitt> ... it seems clear that there are use cases for this to be possible in some way <kbabbitt> ... question is what's the best syntax <kbabbitt> ... possibly extending color-mix but that could be a more complex syntax <kbabbitt> ... not extensible to more than 2 colors <kbabbitt> ... in another issue we resolved to add color-extract function <kbabbitt> ... security and privacy implications so we might not see that anytime soon <kbabbitt> ... it seems to me an extension on existing RCS is probably best way <kbabbitt> ... but still how do we reference components of additional color? <kbabbitt> ... could kick the ball down to authors and ask them <kbabbitt> ... would prefer not to <kbabbitt> ... if there's a user need we could do that later <kbabbitt> ...ideally there should be default names like there are for first color <kbabbitt> ... some might be comfortable with functional syntax eg. c(2) <TabAtkins> q+ <kbabbitt> ... fantasai and I think there re too many parens already <weinig> q+ <ChrisL> prefer c2 to c(2) or c-2 <kbabbitt> ... auto generating idents like c2 seems nicest way <hober> q- <kbabbitt> .... fine for initial vetsion to be limited in # of colors it supports <emilio> q+ <kbabbitt> ... don't thinkm I've seen a quse case requiring more than 3 <kbabbitt> ... if impls want a pre defined max that's fine <kbabbitt> ... other details like what if you're ref'ing an ident and you have fewer colors than that, proposals about that <kbabbitt> ... can iron these out later if there's consensus on general direction <astearns> ack TabAtkins <kbabbitt> TabAtkins: agree with this and generally think lea's ideal case is right way to do it. +1 <astearns> ack weinig <kbabbitt> weinig: one thing I couldn't quite understand: if you want to use channels from 2 different color spaces, would RCS support that? <lea> qq+ <kbabbitt> ... usually RCS extracts channels in single color space <kbabbitt> ... would we need to augment to define extraction? <kizu> q+ <kbabbitt> lea: yes you can nest RCS, each time you convert to color space <kbabbitt> weinig: so if you want lightness to multiply every rgb channel, extract lightness and thne put it in every channel? <kbabbitt> lea: each op is done in one color space <kbabbitt> ... base color can be a relative color <kbabbitt> weinig: say you have a color you want its lightess from <kbabbitt> ... and want to multiply every channel of an rgb color by that value <kbabbitt> .... to brighten its intensity <kbabbitt> lea: why not operate on lightness itself? <kbabbitt> weinig: that's fair <kbabbitt> lea: we can revisit color-extract later which would allow that <kbabbitt> ... not sure there's enough cases but could revisit <kbabbitt> weinig: your argument is strong <ChrisL> In general, doing math on gamma-encoded rgb channel values is almost never useful <kbabbitt> ... also: are there other areas of CSS where extracting parts of it and using those would be useful? <kbabbitt> ... so that instead of color-extract we have a more generic form <kbabbitt> ... to avoid color-mix to mix thing <kbabbitt> ... if we were to go the extract route are there other potential use cases? <kbabbitt> lea: good question, can't think of any offhand <kbabbitt> weinig: don't see any downside to adding support for multiple colors <kbabbitt> ... could do other things if we want <astearns> ack lea <Zakim> lea, you wanted to react to weinig <astearns> ack emilio <kbabbitt> emilio: my question was similar to weinig's <kbabbitt> ...what color space is used if you have multiple <kbabbitt> ...would you get components of each color in its own color space, and ... target? <kbabbitt> lea: this is already defined for RCS <kbabbitt> ... color converted to color space you're working in <kbabbitt> emilio: color space doesn't depend on input, depends on function being used <kbabbitt> lea: precisely <kbabbitt> ... that's how RCS works already <astearns> ack kizu <kbabbitt> kizu: we need something like this <kbabbitt> ...while we still want to have this in situ way of doing things <kbabbitt> ... might also want color-extract <kbabbitt> ... cases where it's difficult to do this in one function <kbabbitt> lea: you can use CSS variables <kbabbitt> kizu: can you assing custom prop with color component and then reuse? <kbabbitt> lea: yes, doesn't resolve until used but could use custom prop for calculations <kbabbitt> kizu: if you are not registering them you could do this <kbabbitt> ... if we are a fan of color-extract for security reasons <kbabbitt> ...one solution might be to do it only in custom functions that return a color <lea> e.g. `--lighter: calc(l * 1.2); color: oklch(from var(--color) var(--lighter) c h);` works fine <kbabbitt> s/are/aren't/ <kbabbitt> [crosstalk] <weinig> q+ <astearns> ack weinig <kbabbitt> weinig: what are the security/privacy concerns with extract color? <kbabbitt> lea: right now you could paint certaing colors on a canvas and read them but... <kbabbitt> weinig: could use gCS to read serialization <kbabbitt> lea: this adds vector to CSS itself <kbabbitt> ...instead of needing JS <kbabbitt> ... minor point but could imagine people raising concerns <kbabbitt> ... e.g. previous meeting accent-color had concerns <lea> or even `--lighter: calc(l * 1.2) calc(c * 1.05); color: oklch(from var(--color) var(--lighter) h);` <kbabbitt> weinig: they have to be resolved for gCS anyway <kbabbitt> ... if we allow that, you can always find out channels yourself <kbabbitt> lea: fair enough <kbabbitt> ... fwiw even if we decide that extract-color is useful, in many cases it would be verbose, this is simpler <kbabbitt> weinig: not objecting just wanted to know what security and privacy concerns were <kbabbitt> astearns: shall we resolve on adding this to spec> <kbabbitt> weinig: I feel we need more debate on syntax for getting components <kbabbitt> ... or maybe a little more thought on if this concept of indexing into an array is something we're creating here <kbabbitt> ...probably not the last time CSS will need indexing into an array of objects <kbabbitt> ... coming up with a syntax we're OK with in future is useful <kbabbitt> astearns: could have this proposal in spec with an issue sayting we need to think about component extraction <kbabbitt> weinig: ok <kbabbitt> astearns: Proposed: Yes to this issue, let's get it in a spec and start work on it <ChrisL> +1 <kbabbitt> RESOLVED: Yes to this issue, let's get it in a spec and start work on it |
There are many use cases that require doing math on components from more than one color, and this is currently impossible without having separate variables for each component.
Example use cases
In the following I'll use an extension of RCS that supports additional colors via the same idents with a number after their name (e.g.
c2
for the second color's chroma while the first one remainsc
). The next section contains a more detailed syntax discussion.Note
Yes, many of these would be better solved with higher level features that are more specific to the use case. However, the argument I'm making is that this is a low-level feature that makes many use cases possible, giving us more time to make them easy, which was also a big motivation behind RCS itself.
1. Combining components from multiple colors
Lightness from one and hue & chroma from another
Applying the same ratio of chromas would take 3 colors (using
blue
as a sort of "template" for the chroma ratio)We've also had several use cases for combining color components from one color and alpha from another but I can't find them right now, one was even high priority as it was needed for a11y. Does anyone have a link handy?
Custom contrasting text color
This also came up when generating text colors automatically. Both this trick, as well as
contrast-color()
generatewhite
andblack
, but in reality you rarely want black (or even "a very dark color"), you want one of your actual design tokens!white
is often acceptable as the light color, but black (or even "a very dark color") rarely is.With multiple colors, you could do (picking between
white
and a dark color:or, to customize both the light and dark color:
If the "repeating the list" idea from below is implemented, the same formula could be used with either 2 or 3 colors, and would just fall back to white if no light color is specified and to white and black if only one color is specified.
Implementing
light-dark()
(if it didn't exist)If
light-dark()
were not a thing, the same formula could be used for that too, to pick one of two colors based on whose lightness was closest to that ofcanvas
(orcanvastext
):Interpolation at a different rate per component
Example: Generating intermediate tints from an accent color and its lightest tint (assumes #11530 is accepted), where chroma typically interpolates at a different rate than other components:
While this may seem complicated, it could be an immense help for #10948.
A design system with the average of 14 scales and 11 tints per hue (source) needs to define 14 * 11 = 154 custom properties, and to pass a color to a component or to change the color of a given element/subtree, one needs to set 11 custom properties. Being able to generate even just the intermediate ones would reduce these to just 3, a 72% reduction.
Implementing two color operations, e.g. blending modes
I've often needed operations like
multiply
on individual colors. Sure, if the need is widespread we could introduce an explicit function, but meanwhile, something as low-level like this allows authors implementing their own (and possibly shipping libraries with entire sets of custom properties for such operations):Once
device-cmyk()
actually ships, this can be used for overprint too:Syntax
Assuming we have consensus that the problem needs solving, how do we solve it?
Some would argue it should be solved in
color-mix()
. I disagree. I think that would make for a much more cumbersome syntax, and is not easily extensible to >2 colors. It would also likely restrict use cases.In #6937 we resolved to add
color-extract()
but that is a more general function, and would result in a lot of verbosity. Also, without restricting it to be used only within color functions, I suspect it could raise security concerns which would slow down implementation even more.I think the nicest solution would be to extend RCS to support multiple colors by simply changing
from <color>
tofrom <color>+
in its grammar. This may even obliterate the need forcolor-extract()
altogether — we should revisit it after to see if there are any remaining use cases for it.Then the question becomes: how do we reference components of the 2nd, 3rd etc color? Some options are:
c2
,c3
etc. Or perhapsc-2
,c-3
etc.c(2)
rather than supporting arbitrary idents. @fantasai and I are not huge fans of the extra parens (we already have too many!) but in the interest of moving the proposal forwards, I would not object to it. One advantage of it would be that it would support variables for the color index without depending on [css-values] A way to dynamically construct custom-ident and dashed-ident values #9141, though that's a small advantage since that's almost certainly shipping before this proposal. 😁Sugar
A nice-to-have would be to also support a
1
version for the first color, i.e.c1
/c-1
/c(1)
becomes an alias ofc
.Another question is, how to deal with components out of bounds? E.g.
c2
being specified when only one color is used. We could treat it as invalid and that would probably be fine. However, I think a better solution that allows more flexibility would be to resolve it against the color list we do have:This way, we can write expressions that account for up to e.g. N colors but fall back gracefully to fewer colors, which can be useful for use cases where we want alternating colors like e.g. charts, syntax highlighting, accented sections etc. See the contrast color use case above for an example of how this could help simplify code.
Layering
If it makes things easier for implementors, shipping a version that only supports up to 3 or even just 2 colors at first would still cover the vast majority of use cases (and the rest can be done by nesting multiple of these). Personally, I don't think I've ever encountered a use case that needed more than 3, actually.
Even in the long run, I think it's fine to set a relatively low upper bound (e.g. 16) for the number of colors that can be specified.
And obviously the sugar above could also be a Level 2 thing (as long as values out of bounds are treated as an error).
The text was updated successfully, but these errors were encountered: