It is difficult to tell if a given browser agent is using light or dark mode UI, and respond to that, without waiting for JavaScript to boot up, or applying a media query in CSS. These existing techniques cover most cases but don't cover all of them. In particular:
Switching the favicon for the page
Applying different images for dark or light mode (block images - background images would be covered by media queries)
Choosing App Manifest values based on dark or light mode (in particular, theme colors/swatches)
Rationale
Switching the favicon is a particularly salient example. With the transition to dark/light mode support, many companies - large and small - are now caught with dark icons that don't work well with dark-mode UIs. Below, an example of "getting it right" vs. not:
If you squint real hard, you can see the Github icon, but Dark Mode doesn't make it easy. Google's approach - a field of white - works in all modes, but also pops when it doesn't need to:
Favicons are not settable via CSS, but they can, of course, be changed with JavaScript. The problems with this approach are likely the cause of both issues above.
Google, and companies like it, want a consistent favicon experience so that users can easily identify their properties. However, to account for the above contrast issues in dark/light mode, Google would be forced to adopt JavaScript code on every single property and page in order to appropriately serve or change the favicon. This is sub-optimal for a number of reasons:
Adding JS to change the favicon incurs a runtime code increase, across all pages and properties, regardless of whether the user ever changes their theme, and for the marginal benefit of a favicon that lives in harmony with the world around it. This is already a tradeoff most developers quite sensibly wouldn't make.
Even worse, this approach is unable to be proactive, because the browser won't wait for JS to load a favicon declared by markup, or one served at the root of the site. In fact, waiting will yield an error by default on some page-loads (at least in Chrome - a 404 for /favicon.ico). At best, there are workarounds (serving a transparent favicon and then substituting with JS), but no optimal way to simply serve the right icon the first time.
As usual, the exclusive advantage of JavaScript here is event-based programming. However, what percentage of users change dark/light mode while on a given web page, versus landing on that web page with dark mode and later leaving, having not changed it? Many fewer, of course, which mitigates this advantage almost entirely.1
Potential Solution: Client Hints
Meanwhile, there is a new feature in HTTP land that seems well-suited to help solve this problem: Client Hints! Let's suppose there was a new Theme hint.
Now, my examples might look like this:
Browser:
GET https://www.google.com/
Google:
200 OK
Accept-CH: Theme
Content-Type: text/html
... later ...
<link rel="shortcut icon" href="/favicon.ico" />
Browser:
GET https://www.google.com/favicon.ico
Theme: Dark
Google: *serves an epic icon for dark mode*
This approach could, of course, be extended to stylesheets and scripts which can now be aware of the theme and serve other resources appropriately. Images aside from the favicon could choose to invert transparency or perform other tricks, and stylesheets/scripts have a chance to adapt as well.
This CH-based approach accomplishes a solution to the favicon problem with a much less invasive approach: (1) the document endpoint must serve Accept-CH (already true), and (2) the assets that change must honor the new hint. Although this change isn't universally trivial, it only touches assets that change, rather than every page on every property, which is a crucial win.
Privacy Concerns
Technologies like Client Hints present an inherent risk of information leakage (i.e. HTTP fingerprinting and related mitigations). Of course, balancing these risks with legitimate performance/experience concerns is always a challenge.
However, Dark mode/Light mode likely doesn't present much of a risky change from a privacy standpoint, for a number of reasons:
1) Dark/light mode is a function supported across most modern operating system UIs (macOS Mojave+2, Windows 10+3, GNOME 3, iOS/iPad OS 13+4, Android 10 (API level 29+)5).
2) There are multiple ways to end up with Dark mode1 (i.e. it is not only a consequence of a static setting chosen by the user). This eliminates any meaning of categorical value for a Dark mode hint from a given client, because for an increasing number of browsing agents, it may simply be the result of a temporal condition (i.e. the time-of-day where the user is).
3) Many users opt to use dark mode full-time. I do not have access to statistics describing how many opt into this feature, but the Chrome team might (( ͡~ ͜ʖ ͡°)). What I can tell from outside Google is, Dark mode has been supported directly since Chrome 73, which was released in February 2019 (over a year ago, as of this writing). Chrome 73+ (again, as of this writing), represents about 63.21%6 of the entire browser-agent footprint internet.
4) Aside from Chrome, Safari 13+ brings in an additional 12.95% of the internet, bringing the total to 76.16% of measured browsers (from Can I use) that could feasibly have dark-mode enabled via a static setting. I bring up this point to illustrate the lack of fingerprint value in a dark mode Client Hint, even when combined with other CH values like RTT, in terms of identifying the approximate location of the user emitting the hints. That is to say, it is perhaps no less risky than the RTT or Theme hint, sent on their own.
5) The Client Hints Delegation proposal on W3C's Feature Policy spec might further mitigate these concerns in a substantial way.
I know this is just a rough start estimating the privacy implications, but I anticipate this may be a concern, so I wanted to surface some thinking on it to get the ball rolling.
Prior Art
I could not locate an existing issue or proposal relating to dark/light mode Client Hints, but of course, I could have missed something.
I agree that it would make sense to expose this (and other user preferences MQs) as a hint. There's an open CSS issue on that front. Would be good to concentrate efforts on missing use-cases there.
In the meantime, I think your use-cases can also be tackled in alternative ways:
Choosing App Manifest values based on dark or light mode (in particular, theme colors/swatches)
While this could take the same route as the favicon (and use the media attribute), app manifests are likely to see user preference changes throughout their lifetime, so neither MQs nor Client Hints seems appropriate. It would be better to bake in conditional values based on user preferences into the manifest format itself. Worthwhile to file an issue on https://github.com/w3c/manifest/issues
I agree that it would make sense to expose this (and other user
preferences MQs) as a hint. There's an open CSS issue
https://github.com/w3c/csswg-drafts/issues/4162 on that front. Would be
good to concentrate efforts on missing use-cases there.
In the meantime, I think your use-cases can also be tackled in alternative
ways:
Switching the favicon for the page
Does
work? If not, worthwhile to file browser bugs to see that it does (because
it should).
Applying different images for dark or light mode (block images -
background images would be covered by media queries)
Choosing App Manifest https://web.dev/add-manifest/ values based
on dark or light mode (in particular, theme colors/swatches)
While this could take the same route as the favicon (and use the media
attribute), app manifests are likely to see user preference changes
throughout their lifetime, so neither MQs nor Client Hints seems
appropriate. It would be better to bake in conditional values based on user
preferences into the manifest format itself. Worthwhile to file an issue on
https://github.com/w3c/manifest/issues
Does
<link rel="shortcut icon" href="/dark_favicon.ico" media="prefers-color-scheme: dark"/><link rel="shortcut icon" href="/light_favicon.ico" media="prefers-color-scheme: light"/>
work? If not, worthwhile to file browser bugs to see that it does (because it should).
A while ago already I have filed crbug/970159 for this (and rel=manifest FWIW).
While this could take the same route as the favicon (and use the media attribute), app manifests are likely to see user preference changes throughout their lifetime, so neither MQs nor Client Hints seems appropriate. It would be better to bake in conditional values based on user preferences into the manifest format itself. Worthwhile to file an issue on https://github.com/w3c/manifest/issues.
As described, this could be handled at the link level, or via the manifest directly. No need to file new issues, there are some already.
If the favicon is an SVG the prefers-color-scheme media query within the
SVG can be use to provide light and dark mode icons in a single asset
thank you everyone for getting back to this idea so quickly.
@andydavies that trick is super cool. i will be employing that from now on
@yoavweiss understood, although that markup might work for the favicon change, it still requires a change in the page HTML itself, which incurs changes across every page for a site, rather than allowing the favicon to adapt to browser conditions on its own. I'm only naming this as an advantage/disadvantage, otherwise of course you are correct (Fwiw I didn't know about media=, thank you!). OTOH, your approach does solve the initial flash of unstyled icon, so at the very least the favicon would be served correctly the first time, which is my major gripe.
@tomayac thank you for your hard work getting SVG favicons through, I was not aware that was even an option. I am laughing to myself that that CR bug was updated just 3 days ago. i guess I have impeccable timing
the amazing response on this issue shows exactly why the web platform is as robust as it is - thank you for considering my idea, and taking the time to offer cogent and valuable feedback 😄
part of the motivation of this proposal would be to allow site operators to override just the /favicon.ico handler to make it dynamic w.r.t. the initial theme selection on page load
on the other hand, I suppose "modern" sites (the kind taking advantage of CH) would probably not be using /favicon.ico at all, but rather <link rel="shortcut icon"... which allows for media=.
there doesn't seem to be much advantage either with regard to caching, except that sending Vary: Theme would allow a cached version for each theme, whereas an SVG prepared as described above would already be cached and available.
@yoavweiss understood, although that markup might work for the favicon change, it still requires a change in the page HTML itself, which incurs changes across every page for a site, rather than allowing the favicon to adapt to browser conditions on its own. I'm only naming this as an advantage/disadvantage, otherwise of course you are correct (Fwiw I didn't know about media=, thank you!). OTOH, your approach does solve the initial flash of unstyled icon, so at the very least the favicon would be served correctly the first time, which is my major gripe.
Just to be clear, I wasn't arguing against exposing the dark mode as a client hint, just pointed out an alternative way to tackle the use case today. But as you say, markup change across all the site's pages has its own costs, and there are cases and stacks where server-side adaptation makes more sense.
@yoavweiss ah, understood. this is my first time proposing something such as this, i suppose in these circumstances it isn't a bad idea to have more than one way to do something. forgive me for calling it "your" approach as both are entirely valid - i just didn't know about media=, and now i do! thank you 🙇
Problem
It is difficult to tell if a given browser agent is using light or dark mode UI, and respond to that, without waiting for JavaScript to boot up, or applying a media query in CSS. These existing techniques cover most cases but don't cover all of them. In particular:
favicon
for the pageRationale
Switching the
favicon
is a particularly salient example. With the transition to dark/light mode support, many companies - large and small - are now caught with dark icons that don't work well with dark-mode UIs. Below, an example of "getting it right" vs. not:If you squint real hard, you can see the Github icon, but Dark Mode doesn't make it easy. Google's approach - a field of white - works in all modes, but also pops when it doesn't need to:
Favicons are not settable via CSS, but they can, of course, be changed with JavaScript. The problems with this approach are likely the cause of both issues above.
Google, and companies like it, want a consistent favicon experience so that users can easily identify their properties. However, to account for the above contrast issues in dark/light mode, Google would be forced to adopt JavaScript code on every single property and page in order to appropriately serve or change the favicon. This is sub-optimal for a number of reasons:
Adding JS to change the
favicon
incurs a runtime code increase, across all pages and properties, regardless of whether the user ever changes their theme, and for the marginal benefit of afavicon
that lives in harmony with the world around it. This is already a tradeoff most developers quite sensibly wouldn't make.Even worse, this approach is unable to be proactive, because the browser won't wait for JS to load a
favicon
declared by markup, or one served at the root of the site. In fact, waiting will yield an error by default on some page-loads (at least in Chrome - a 404 for/favicon.ico
). At best, there are workarounds (serving a transparentfavicon
and then substituting with JS), but no optimal way to simply serve the right icon the first time.As usual, the exclusive advantage of JavaScript here is event-based programming. However, what percentage of users change dark/light mode while on a given web page, versus landing on that web page with dark mode and later leaving, having not changed it? Many fewer, of course, which mitigates this advantage almost entirely.1
Potential Solution: Client Hints
Meanwhile, there is a new feature in HTTP land that seems well-suited to help solve this problem: Client Hints! Let's suppose there was a new
Theme
hint.Now, my examples might look like this:
Browser:
Google:
Browser:
Google: *serves an epic icon for dark mode*
This approach could, of course, be extended to stylesheets and scripts which can now be aware of the theme and serve other resources appropriately. Images aside from the favicon could choose to invert transparency or perform other tricks, and stylesheets/scripts have a chance to adapt as well.
This CH-based approach accomplishes a solution to the favicon problem with a much less invasive approach: (1) the document endpoint must serve
Accept-CH
(already true), and (2) the assets that change must honor the new hint. Although this change isn't universally trivial, it only touches assets that change, rather than every page on every property, which is a crucial win.Privacy Concerns
Technologies like Client Hints present an inherent risk of information leakage (i.e. HTTP fingerprinting and related mitigations). Of course, balancing these risks with legitimate performance/experience concerns is always a challenge.
However, Dark mode/Light mode likely doesn't present much of a risky change from a privacy standpoint, for a number of reasons: 1) Dark/light mode is a function supported across most modern operating system UIs (macOS Mojave+2, Windows 10+3, GNOME 3, iOS/iPad OS 13+4, Android 10 (API level 29+)5).
2) There are multiple ways to end up with Dark mode1 (i.e. it is not only a consequence of a static setting chosen by the user). This eliminates any meaning of categorical value for a Dark mode hint from a given client, because for an increasing number of browsing agents, it may simply be the result of a temporal condition (i.e. the time-of-day where the user is).
3) Many users opt to use dark mode full-time. I do not have access to statistics describing how many opt into this feature, but the Chrome team might (
( ͡~ ͜ʖ ͡°)
). What I can tell from outside Google is, Dark mode has been supported directly since Chrome 73, which was released in February 2019 (over a year ago, as of this writing). Chrome 73+ (again, as of this writing), represents about 63.21%6 of the entire browser-agent footprint internet.4) Aside from Chrome, Safari 13+ brings in an additional 12.95% of the internet, bringing the total to 76.16% of measured browsers (from Can I use) that could feasibly have dark-mode enabled via a static setting. I bring up this point to illustrate the lack of fingerprint value in a dark mode Client Hint, even when combined with other CH values like RTT, in terms of identifying the approximate location of the user emitting the hints. That is to say, it is perhaps no less risky than the RTT or Theme hint, sent on their own.
5) The Client Hints Delegation proposal on W3C's Feature Policy spec might further mitigate these concerns in a substantial way.
I know this is just a rough start estimating the privacy implications, but I anticipate this may be a concern, so I wanted to surface some thinking on it to get the ball rolling.
Prior Art
I could not locate an existing issue or proposal relating to dark/light mode Client Hints, but of course, I could have missed something.
Citations
1: macOS now supports dynamically switching from dark/light and back based on the current time-of-day, so this may happen more often now. 2: "How to use Dark Mode on your Mac" 3: "Windows 10 dark mode is here. Turn it on now" 4: "iOS Human Interface Guidelines: Dark Mode" 5: "Android Developers: Dark Mode" 6: "Can I use: Browser Table"