Querying the Color Scheme
- Published on:
- Categories:
- Style Queries 2, color-scheme, CSS Variables 5, CSS 61
- Current music:
- The Album Leaf — Dust Collects
- Current drink:
- Lemongrass, Ginger & Black Pepper tea
Introduction
Media queries are nice: they allow us to query different features, like the prefers-color-scheme
one, which allows us to get the user preference and switch some styles between light and dark themes.
For many things, we don’t even need the media queries themselves: there is this great CSS property color-scheme
. If we set it on the root like this:
:root {
color-scheme: light dark;
}
Many things in our page will automatically adapt to the user’s color-scheme:
- Built-in UI elements: scrollbars, inputs, buttons.
- Some system colors: for example,
Canvas
andCanvasText
. - The built-in
light-dark()
function, which accepts two colors and returns the first one when the theme is light, and the second one otherwise.
Adapting to the User Preference
By providing both possible schemes: “light dark
” to the color-scheme
, we tell the browser that it is ok to adapt to one of those themes that matches the user preference. The example below should adapt to the color-scheme you’re using in your browser:
We can see how everything — the light-dark()
, the Canvas
& CanvasText
and the media queries — adapts to the current scheme.
Enforcing a color-scheme
But what if we will set only one value?
While system colors and light-dark()
applied according to our color-scheme
, we can’t change the media query from our CSS. It just tells us what is the user preference.
The light-dark()
itself is very useful, but can only be used for things that expect an actual CSS <color>
type. But what if we’d want to adapt other, non-color things?
We can work around this by using something different as the source of truth, like CSS scopes or style queries. I recommend reading the recent “Page and Component Adaptive Light/Dark” post by Adam Argyle about these.
But what if we’d want to use the color-scheme
as the source of truth?
Single Source of Truth
With the style queries and registered custom properties, we could! Here is how:
Here, instead of using media queries, we register a --captured-color
custom property, then assign two different values to it using the light-dark()
function. Because the property is registered, the function is properly applied, resulting in the corresponding color changing based on the color-scheme
.
Then, instead of relying on the prefers-color-scheme
, we use a container style query to query this registered custom property, which allows us setting any properties for anything inside the element that defines the color-scheme
!
Downsides
The main downside of this method (outside the browser support) is the fact that the style queries apply to the elements inside the element with the color-scheme
, but the color-scheme
changes the styles on the element itself. Unless we’ll get some way to conditionally apply styles on the element itself, we will need to make sure we never style anything on the element with the color-scheme
.
And, of course, it is not as intuitive with the container style queries targeting some variable with some abstract values.
Not the User Preference
In the most recent “Web-Standards podcast” (in Russian), Vadim Makeev mentioned a good point: there is a big difference between the user preference and a color-scheme
property. Occasionally, it might be alluring to use the color-scheme
as a switch for the components’ theme, but we need to consider that even when we do so, we could still want to listen to the prefers-color-scheme
to understand which theme the user prefers, and make adjustments to both the light and dark themes we’re applying via color-scheme
.
For example, if the user prefers the dark
color-scheme, and we’re overriding it to light
on some inner component, we could want to not just invert the colors there, but also make them not as bright, as in not to make it stand out too much. We might even want to adjust the overall theme based on it if we have the built-in color scheme switch: dim the light
one when the user prefers the dark
color scheme, and make the dark
more contrast if the user has it as light
, as otherwise UI elements could be overshined by the bright browser chrome.
The Future
I did not find a dedicated issue about this yet, but in one of the other issues about color-scheme
many people did express their desire to have a dedicated style query for this. I imagine it will work very similarly, and potentially have the same downside of not matching with the color-scheme
, unless there will be some specific handling implemented that will prevent any circular dependency issues.
The abovementioned issue itself is about the problem where a <meta name=color-scheme>
in HTML does not reflect on the prefers-color-scheme
@media
. In my opinion, this is the way it should work: I think the user preferences should stay that way, and it is not correct to change it based on the current color scheme, regardless of where it is defined — in HTML or CSS.
This technique (or a potential style query) will solve the issue described in the issue well enough: just apply it on the html
or body
element and use it instead of the @media
itself.
Conclusion
Registered custom properties are powerful in their ability to capture some value on the element itself, rather than passing it down to be applied later. I got the idea to apply it to the light-dark()
function when playing with the tan(atan2())
technique as a part of my experiments for the “Fit-to-Width Text: A New Technique” article, and after playing with the style queries for my “Self-Modifying Variables: the inherit()
Workaround” article.
As always, I am fascinated by what we can achieve by combining different CSS features: in this case we rely on three of them together. I hope this post will encourage you to experiment with all the new things in CSS as well, and will give you ideas about how we could use them in other unusual ways.