Open emilio opened 1 year ago
Big fan of the -internal-light-dark()
function here as well. Would make some things easier.
A few questions/remarks:
With the proposed schemed-color(<custom-ident> <color>, <fallback-color>)
, how would support for multiple schemes work?
Do you suggest to nest schemed-color()
– e.g. schemed-color(light red, schemed-color(dark blue, pink))
– or should we allow multiple entries by changing the syntax to schemed-color([<custom-ident> <color>]#, <fallback-color>)
?
The suggested schemed-color
only caters for colors, whereas light-dark()
allows things other than colors. This seems pretty limiting. Can we open it up to have it return any value instead of a <color>
? So the function would maybe become schemed-value()
?
I’m not an expert on CSS syntax, but I guess the syntax would then become this: schemed-value([<custom-ident> <declaration-value>]#, <declaration-value>)
Anecdotally: The function in Chromium first was called -internal-light-dark-color()
but then got renamed to -internal-light-dark()
Maybe light-dark()
could be kept, and become a alias (limited to only two schemes) for schemed-color
?
Or if we should not want to pursue this, maybe we can leave it up to authors to define their own custom function once that becomes a thing?
With CSS Nesting – and potentially implicit & { }
along with that – around the corner, this would become more easy for authors to do:
No Nesting:
el {
--img: url(/img/bg-light.png);
background: transparent var(--img) no-repeat 0 0;
}
@media (prefers-color-scheme: dark) {
el {
--img: url(/img/bg-dark.png);
}
}
@media (prefers-color-scheme: other) {
el {
--img: url(/img/bg-other.png);
}
}
With nesting:
el {
--img: url(/img/bg-light.png);
background: transparent var(--img) no-repeat 0 0;
@media (prefers-color-scheme: dark) {
--img: url(/img/bg-dark.png);
}
@media (prefers-color-scheme: other) {
--img: url(/img/bg-other.png);
}
}
However, the nesting version doesn’t beat schemed-value()
imo, so authors would still benefit from having such a function available:
el {
--img: schemed-value(
dark url(/img/bg-dark.png),
other url(/img/bg-other.png),
url(/img/bg-light.png)
);
background: transparent var(--img) no-repeat 0 0;
}
I propose to go for light-dark(<foo>, <bar>)
, then extend to schemed-color
or schemed-value
in the future, if needed?
I'd propose to do this for <color>
(or <color>
and <image>
) for now, rather than everything.
lol, schemed-color()
rules as a name. (But probably isn't what we want to go with. ^_^)
But given that we purposely designed color-scheme to be extensible for the future, we should do the same here. (But I think we can optimize too.)
I propose the grammar be <color>#{2} | [ <custom-ident> <color> ]#{2,}
- the first branch is just light and dark, the second lets you name the color schemes you want to associate it with.
In many cases, authors would want to toggle a single channel value (e.g. lightness) within a color. They can do that by repeating the entire color - but allowing <number>
/<length>
sure would simplify that use-case.
We need to either know the value type ahead of time, or lean on var() semantics. So we could do the schemed-color() / schemed-value() distinction that Bramus suggests, with the two behaviors.
In many cases, authors would want to toggle a single channel value (e.g. lightness) within a color. They can do that by repeating the entire color - but allowing
<number>
/<length>
sure would simplify that use-case.
Yeah I didn't want to open this to arbitrary lengths and such because that adds a bunch of new property dependencies that I haven't thought through... Though using var() semantics probably works for that case?
However, the nesting version doesn’t beat
schemed-value()
imo, so authors would still benefit from having such a function available:el { --img: schemed-value( dark url(/img/bg-dark.png), other url(/img/bg-other.png), url(/img/bg-light.png) ); background: transparent var(--img) no-repeat 0 0; }
Additionally, schemed-value() can do something the media query cannot, because prefers-color-scheme matches the same across the document while schemed-value() respond to the per-element color-scheme:
<style>
div { background-color: schemed-value(dark red, light green); }
</style>
<div style="color-scheme:light">Green background</div>
<div style="color-scheme:dark">Red background</div>
The CSS Working Group just discussed [css-color] Add a function to allow authors to specify colors reacting to `color-scheme`
, and agreed to the following:
RESOLVED: Add light-dark() fuction that returns a color based on the color scheme.
Just saw this from @bramus's post, and I suspect that things are already closed / there's no changing things now, but I see this as an approach that doesn't actually solve for the issues people are facing with theming, and does so in a way that will create a trap for them when pursuing proper theming support.
As a very simple example, what happens when a site that uses this grows to the point where they need to add a high contrast mode? At that point this will require a refactor.
The other element I'm concerned with here is it all but seals the deal that color-scheme
will never grow beyond light|dark, despite custom identifiers being allowed for that field. What I'd much prefer is a solution that scales, rather than something that only helps one specific use case.
As a simple example, consider if it were implemented this way instead:
:root {
color-scheme: light dark high-contrast;
}
body {
background: scheme(light #eee, dark #333, high-contrast #fff);
}
With this model, you have something that scales beyond light and dark, but now supports visual accessibility modes such has high contrast/colorblind modes. You could also support multiple brands with this, i.e.
:root {
color-scheme: docs spreadsheets slides;
}
body {
color: scheme(docs blue, spreadsheets green, slides yellow);
}
We shouldn't ship single-purpose tools for the browser, but rather ones that scale and we can build upon.
things are already closed […]
I think this was mistakingly done so. The end goal is to have something like schemed-value()
, with light-dark()
being an intermediary step towards the final solution.
This is also reflected in the meeting minutes: go for light-dark()
now, and extend to more color schemes and other types of values in the future.
(I’m hereby reopening the issue)
As a simple example, consider if it were implemented this way instead:
[…]
The syntax you propose rhymes with was suggested in the original post. I like the suggestion to have the function accept an arbitrary number of scheme-value pairs. Much handier than needing to nest one schemed-value()
into the other.
Side note: I’ve added an extra section to my post to explicitly mention that light-dark()
is not the end goal but schemed-color()
/schemed-value()
is. This will hopefully remove some of the confusion.
As a very simple example, what happens when a site that uses this grows to the point where they need to add a high contrast mode? At that point this will require a refactor.
I've always thought of things like high-contrast as somewhat orthogonal to light/dark, for what is worth. Something can still be light or dark and high-contrast.
For the record, I think light-dark
is still pretty useful for high contrast. Think of the simple example:
:root {
--text-color: light-dark(#ddd, #333);
@media (prefers-contrast) {
--text-color: light-dark(black, white);
}
}
The other element I'm concerned with here is it all but seals the deal that
color-scheme
will never grow beyond light|dark, despite custom identifiers being allowed for that field. What I'd much prefer is a solution that scales, rather than something that only helps one specific use case.
FWIW, my understanding is that color-scheme
was never intended to provide custom theme capabilities like suggested above, but is a way to expose the browser's color-scheme support. It might be worth filing an issue to discuss that, if that's something people want.
As a simple example, consider if it were implemented this way instead:
:root { color-scheme: light dark high-contrast; } body { background: scheme(light #eee, dark #333, high-contrast #fff); }
As @bramus said, we did have this discussion. light-dark
is still fairly useful regardless, and allows you to exploit the color-scheme
property properly in the way the browser already does with system colors.
Anecdata, but in my experience with the Firefox front-end it's gotten quite a bit of adoption (see uses).
Firefox does support all sorts of themes, and that didn't make us not use this. Quite the opposite, it did simplify the theming setup quite a bit, from icons, to link colors, etc...
As soon as you have a complex enough theming setup, you need parts of the UI to be light and parts to be dark, and light-dark
allows you to react to these like the browser does.
It seems you kinda want a more general theming mechanism than what the color-scheme
property provides. And that's fair, maybe we should extend that property to not only expose built-in color schemes, or have a separate mechanism, or something. I encourage you to file an issue about it (with or without a proposal, that's ok). But FWIW I don't think that makes light-dark()
here less useful (though I'm obviously somewhat biased).
I'm happy to hold off shipping light-dark()
on Firefox if people thing it's fundamentally wrong, or a step in the wrong direction that would prevent us from doing something that we might want like extending color-scheme
to support custom author-provided palettes, somehow, but I don't think it's the case.
As part of dealing with this issue:
I looked again at the definition of <color>
and in particular <absolute-color-base>
which is used, for example, in the definition of the override-colors
descriptor in @font-palette-values
.
<absolute-color-base>
is a subset of <color>
which excludes the following values, all of which have special handling (like depending on layout, resolving late, not having a defined colorimetric interpretation):
A consequence of adding light-dark()
directly to <color>
rather than to <absolute-color-base>
is that it can't be used in, for example, override-colors
. I suspect this was unintentional, and propose to add it to <absolute-color-base>
instead (along with color-mix()
which is also missing from the grammar.
@emilio @bramus was the intent to exclude light-dark()
from use in override-colors
? Or was that unintentional and I should move it?
I'm happy to hold off shipping
light-dark()
on Firefox if people thing it's fundamentally wrong, or a step in the wrong direction that would prevent us from doing something that we might want like extendingcolor-scheme
to support custom author-provided palettes, somehow, but I don't think it's the case.
The sentiment on social media (X, Mastodon) is generally in favor.
Recurring comments I’ve heard are:
color-scheme
values? The spec has built-in wiggle room to eventually allow these in the future, but reality is that these are currently not allowed.These issues seem both unrelated to light-dark()
itself.
@emilio @bramus was the intent to exclude
light-dark()
from use inoverride-colors
? Or was that unintentional and I should move it?
I can’t gauge the consequences of doing that, so I’ll leave that decision up to Emilio and one of the color experts here – a certain Chris, I think you know him ;) – to make.
@svgeesus: light-dark()
resolves late, based on the computed color-scheme. So unless @font-palette-values
can provide a color-scheme, it can't be there, right? color-mix()
can contain <currentColor>
recursively, so I suspect it's a bit more complicated than just moving it there.
Aha okay, I see. It is in the right place, then.
But yeah I was just discussing with @LeaVerou color-mix()
and RCS already break the assumption that the absolute ones are absolute. You can always color-mix(in oklab, peru, currentColor)
or rgb(from currentColor r g b)
. So what <absolute-color-base>
was trying to achieve by way of grammar needs to be expressed in prose, it seems. (Or a really, really unweildy and unreadable grammar),
What I proposed to @svgeesus was invert this breakdown:
<color> = <color-function> | <hex-color> | <color-relative-basic> | <named-color> | ...
<color-relative-basic> = currentColor | <system-color> | device-cmyk()
Then also define a <color-relative>
token, with prose along the lines of "Any <color>
which includes <color-relative-basic>
anywhere in its tree (note that this could be described as a grammar, it would just be a grammar that no human would want to ever read or edit). Then that token can be referenced as needed, from any other spec.
Another advantage of this is that it maintains a coherent definition of <color>
abstracting away only the weirdness into a separate token, whereas the current breakdown has the weirdness up front and center.
Yeah it's probably worth defining the concept of "absolute-color" or something in prose.
Some UIs require rendering a light button in some places, but dark in another. It'd be nice if you could switch between these by using
color-scheme
, like you can with native widgets. However, right now there's no way to do this other than exclusively using system colors.Chromium has
-internal-light-dark(<light-color>, <dark-color>)
for this. Howevercolor-scheme
was designed a bit more generically thanlight
/dark
, so maybe we need aschemed-color(<custom-ident> <color>, <fallback-color>)
or so, where the<custom-ident>
is the scheme name, and the<color>
is the color for that scheme. So-internal-light-dark(red, blue)
would becomeschemed-color(light red, blue)
or so.This would allow authors to create widgets that properly react to
color-scheme
.