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

[mediaqueries-5] Script control of (prefers-*) queries #6517

Open
tabatkins opened this issue Aug 13, 2021 · 34 comments
Open

[mediaqueries-5] Script control of (prefers-*) queries #6517

tabatkins opened this issue Aug 13, 2021 · 34 comments

Comments

@tabatkins
Copy link
Member

In this Discourse thread, jimmyfrasche requests the ability for pages to override the (prefers-*) MQs in script, so they can easily integrate a manual toggle in the page without having to do silly duplication of their styles in both an MQ and a selector.

My initial response was that custom MQs would solve this, but they're actually somewhat non-trivial to do.

The trivial solution of "just make a script-based custom MQ that initially defers to (prefers-color-scheme), and can be overridden to true/false if you manually toggle" means that if script doesn't load, you have an unknown (--dark-mode) MQ in your page that is just always false, so you apply light-mode styles even if the user normally wants dark mode.

Instead you must double-layer them:

<style>
@custom-media --dark-mode (prefers-color-scheme: dark);
/* use @media (--dark-mode) {...} for stuff */
</style>
<script>
/* Assuming that a JS registration overrides any existing CSS-based ones */
CSS.customMedias.set("--dark-mode", ...);
</script>

And this only works for boolean MQs. If they're multi-state, it's trickier; you have to set up distinct custom MQs for each of the states and override all of them in script.

Would it instead make sense to allow authors to override at least the (prefers-*) queries with CSS.customMedias, supplying a CSS value that's valid for the MQ in question? That way you could instead write:

<style>
@media (prefers-color-scheme: dark) {...}
/* always works */
</style>
<script>
/* override when manual toggle is engaged */
CSS.customMedias.set("prefers-color-scheme", ...);
</script>

(I don't see a particular reason to limit it to only the (prefers-*) queries, but nor do I see a reason to allow it for things like (width). I mildly lean towards allowing override of anything, just so we don't have to think about which ones are allowed and which aren't and keep that list up to date, but I could easily be convinced otherwise.)

@jimmyfrasche
Copy link

Thank you for filing the issue @tabatkins

While out of scope, I do think it's important to consider web extensions here. Namely, a very natural extension is one that lets you toggle preferences per site and if there's only one thing to set then there's a race between the site and the extension: if they both attempt to override the preference the last one to do so wins.

That will be confusing for users and a perennial source of consternation for the poor maintainer fielding bug reports.

I suppose if the browser could tell whether an extension or a site updated a preference media query then they could use that information to apply precedence but that seems over complicated making it less likely that they're willing to do anything. I'm not sure how they would do that unless CSS is available to web extensions or browsers track scripts injected into sites.

Also unless overriding media queries rolls out with custom media queries how would you feature test this without overrding a preference which could itself mess with such an extension.

@tabatkins
Copy link
Member Author

Namely, a very natural extension is one that lets you toggle preferences per site and if there's only one thing to set then there's a race between the site and the extension: if they both attempt to override the preference the last one to do so wins.

Sure, but why would you use both a site-based and an extension-based toggle on the same page? One or the other would work fine, so I don't think we need to worry about colliding.

(And it's not like the extension can guarantee things anyway, since the page might use only a manually-set light/dark pref, initialized from the standard MQ.)

Also unless overriding media queries rolls out with custom media queries how would you feature test this without overrding a preference which could itself mess with such an extension.

In this suggestion, overriding is part of custom MQs, so they'd go out together.

@daKmoR
Copy link

daKmoR commented May 28, 2022

oh I somehow missed this issue before 🙈

made a comment on a related chromium bug
https://bugs.chromium.org/p/chromium/issues/detail?id=1046660#c46

which guided be back here... (I searched here before - I swear 🙈)

let me copy it in here

Should we expand this issue to also talk about a JavaScript API for setting a "per website preference toggleable on the website itself".

Some ideas are for something in the spirit of window.setMedia('prefers-color-scheme', 'dark');

  1. Blog post which problem such a setting would solve https://www.bram.us/2022/05/25/dark-mode-toggles-should-be-a-browser-feature/
  2. Issue reply with :media() is not solving this use case [selectors][mediaqueries] :media() pseudo-class as a shortcut for one-off media queries #6247 (comment)

And to give it some "weight" and backstory I am from the Design System Team from the ING Bank and we struggle to scale dark mode for our design system based on a shared attribute compared to "just" using a @media (prefers-color-scheme: dark) {

Why? Because aligning different teams in different countries with different knowledge and requirements around web standards is "pretty" straight forward... but aligning a custom attribute (where everyone may have a different idea on where to put it, how to name it) is hard.

PS: if this is not the right place - any pointers?

so it seems here a more appropriate place? 😬

the ideas "sketched out "in both of these links in the quote are very similar to CSS.customMedias.set("prefers-color-scheme", ...);

any way we can help? maybe there is more details needed for the problem space it would solve? I think for the API - there is nothing more to it?

I sort of think that this is all you would need to get a fully custom dark mode that persists while reloading or navigating ... which is like soooo much less than all the other (attribute) workarounds 😅

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Dark Mode Switch Example (localstorage for persistence)</title>
    <script>
      const appColorScheme = localStorage.getItem('my-app-color-scheme');
      // restore saved custom color scheme if set
      if (appColorScheme) {
        CSS.customMedias.set('prefers-color-scheme', appColorScheme);
      }
      function changeColorScheme(colorScheme) {
        localStorage.setItem('my-app-color-scheme', colorScheme);
        CSS.customMedias.set('prefers-color-scheme', colorScheme);
      }
    </script>
    <style>
      /* light mode is default */
      p {
        color: black;
        background: white;
      }
      @media (prefers-color-scheme: dark) {
        /* dark mode rules */
        p {
          color: white;
          background: black;
        }
      }
    </style>
  </head>
  <body>
    <p>some text</p>

    <hr />
    <p>Light/Dark preference will be stored for reloads/page navigation</p>
    <button onclick="changeColorScheme('light')">go light</button>
    <button onclick="changeColorScheme('dark')">go dark</button>
  </body>
</html>

@jimmyfrasche
Copy link

If I do CSS.customMedia.set('prefers-color-scheme', 'dark'); will iframes on the page respond to (prefers-color-scheme: dark)? I would expect them to as you don't want all the page to be dark except a weather widget, and it's no different than a user changing the setting from the iframe's perspective.

But, conversely, I wouldn't expect an iframe to be able to override the containing document's preference.

@daKmoR
Copy link

daKmoR commented May 30, 2022

I guess that depends on the content of the iframe?

image the typical Wordpress theme "browser" where you have the next/previous theme buttons and some info about the theme store at the top and below that you have an iframe of the actual theme...
if such a theme comes with its own theme switcher then yes I would expect that iframe to be able to set its own preferred color scheme.

a weather widget will probably not come with its own light/dark mode switch... and even if it does it will hopefully not persist that setting (for example via localstorage)

but yes ultimately how to handle default values is in the hands of the implementer - IMHO rightfully so

@jimmyfrasche
Copy link

My question was how overrides propagate in an arbitrary tree of nested browsing contexts. There are three possibilities:

It could only apply to the context that does the override but then you have a light mode iframe in a dark mode page when it would be homogeneous without an override.

It could propagate to all contexts simultaneously.

It could propagate only to children.

@daKmoR
Copy link

daKmoR commented May 31, 2022

imho it should behave the same as the DevTools switch...
e.g. everyone gets the change by default

see example
Codepen (https://codepen.io/daKmoR/pen/KKQoQjj?editors=0100) + video

light.dark.mp4

But the iframe content can do as it needs e.g. it could

  • do nothing (then it will just get the parent's context)
  • manually sync to parent context selection
  • manually override

In any case, if the frame adds code to change the prefers-color-scheme then it becomes responsible for its value and it can decide when/how/why to sync or not

PS: this assumes prefers-color-scheme is/will be a document level setting... e.g. not a browser-wide/system-wide setting

@bennypowers
Copy link

bennypowers commented Jun 10, 2022

PS: this assumes prefers-color-scheme is/will be a document level setting... e.g. not a browser-wide/system-wide setting

should this be per-origin?

@emilio
Copy link
Collaborator

emilio commented Jun 10, 2022

mozilla/wg-decisions#774 is also related here. In the Firefox UI for example, we set prefers-color-scheme in the root based on the firefox theme, and it inherits to all frames from there.

@emilio
Copy link
Collaborator

emilio commented Jun 10, 2022

Err, meant #7213 of course, sorry.

@daKmoR
Copy link

daKmoR commented Jun 10, 2022

should this be per-origin?

That seems reasonable to me 👍

@tabatkins
Copy link
Member Author

Yeah, we can't introduce a new communication channel to cross-origin iframes, so the preference can't trickle down everywhere. It def should go down origins that can communicate, tho.

But #7213 does bring up the point that SVG-as-image can't run script or load external resources, so it shouldn't be able to exfiltrate any information. Whatever we do, we should match there.

@jimmyfrasche
Copy link

Is it communications? Is it observably any different from the user popping open OS settings and changing it there?

@tabatkins
Copy link
Member Author

Yes, because it's the site changing things. It's fine if the user communicates with arbitrary sites, as they can already do that by visiting the site. What we don't want to allow is sites to exfiltrate data to, say, ad exchanges, particularly when we're exploring things like fenced frames to close out the few bits of cross-origin communication left.

@lukewarlow
Copy link
Member

I've just come across this issue while doing some research for a proposal I made to WICG. Its a different API shape (not using custom media queries) but the same overall goal. Let developers make use of preference media queries (along with client hints, color-scheme property etc) while also being able to override them. I'd be curious for people's thoughts on my proposal?

WICG/proposals#117

@tabatkins
Copy link
Member Author

That explainer looks pretty reasonable as the way to control preference MQs!

Its a different API shape (not using custom media queries)

The "correct" solution was never intended to involve custom MQs, fwiw, that was just listed as the way authors would be able to work around the lack of a preference override if we never did anything. Overriding preferences directly is definitely better.

I'd be curious for people's thoughts on my proposal?

Overall I think your design looks just fine. Note the discussion in this thread about which descendant frames can see the overridden preference; any cross-origin frame that can communicate with the external world shouldn't see them (and should instead just get the original real user preference). But same-origin frames, and cross-origins that can't communicate, should be able to see the overrides (probably invisibly; that is, they'll just see it as user's preference). I'll open some issues over on your repo.

@astearns astearns added this to Unslotted in TPAC 2023 agenda Sep 14, 2023
@LeaVerou
Copy link
Member

Btw this was also designed to address this problem and a few more: #6247

Though these days we might need a more general :if() or :condition() pseudo-class.

@tabatkins
Copy link
Member Author

:media() solves the "difficult to style based on both an MQ and a class" problem, but this proposal solves more issues (for the set of the MQs that it's trying to address). The intro to the draft spec has a great explanation of what all it addresses.

@astearns astearns moved this from Unslotted to Friday Morning in TPAC 2023 agenda Sep 14, 2023
@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [mediaqueries-5] Script control of (prefers-*) queries, and agreed to the following:

  • ACTION(fremy): Look at the proposal, see whether there's an issue to be filed
  • ACTION(TabAtkins): Determine if prefers-reduce-data is handled
The full IRC log of that discussion <myles> Topic: [mediaqueries-5] Script control of (prefers-*) queries
<myles> github: https://github.com//issues/6517
<myles> TabAtkins: some time ago i opened this issue so it would be useful to allow script control of the prefers media queries. simple case: if you were doing a dark mode toggle on your site (explicit switch) independent of their UA/OS, it's awkward to style
<myles> TabAtkins: you want to be able to pay attention to people's OS styles when their OS preferences when they haven't indicated a preference, but use the toggle when they have.
<myles> TabAtkins: Writing styles based on when a class in HTML exists is awkward. The examples are terrible, you have to repeat yourself
<myles> TabAtkins: The most straightforward way to fix this is to allow people to directly control the media query
<myles> TabAtkins: There are some routes to address some aspects of this problem
<myles> TabAtkins: Another option: a media pseudoclass. so you could name that in the block. But that would just solve one problem
<myles> TabAtkins: A person posted a proposal for the API for controlling this. He gives a great example of multiple problems we solve by this.
<astearns> https://github.com/WICG/proposals/issues/117
<myles> TabAtkins: Namely: IF you want to a dark mode toggle by the user, it would be great if you could use this for stylesheet loading. right now, that wouldn't work.
<TabAtkins> https://github.com/lukewarlow/web-preferences-api/blob/main/README.md
<myles> TabAtkins: Any of the media matches API, it would be good to have it respond appropriately
<myles> TabAtkins: I think this looks good
<myles> TabAtkins: I think what's in the proposal is good
<myles> TabAtkins: It can be iterated on
<myles> TabAtkins: We should take it to any group to iterate and adopt it appropriately
<bramus> q+
<myles> TabAtkins: The main question: Does solveing this problem sound sufficiently useful to the group? Is the rough shape (a JS API where you explicitly control the media queries for your site and descendent frames) reasonable?
<myles> TabAtkins: There's an implied persistence model here: Once you set the toggle, it stays toggled (so you don't have re-up the toggle on the next load)
<emilio> q+
<myles> TabAtkins: _how_ persistent it should be is worthy of discussion. But it's should be able to toggle and keep it toggled for future visits
<myles> TabAtkins: is this the right direction? Should we take it? Should we send it to webapps?
<astearns> ack bramus
<myles> bramus: I'm a big fan of this proposal. Authors are having this problem.
<myles> bramus: Right now authors need to duplicate a bunch of their CSS
<myles> bramus: They also need more blocking JS on page load
<myles> bramus: it would be really nice to get this
<myles> bramus: For the CSS-only type of approach where you have your setting and your override, setting the preference inside the browser ... <missed> already has this, it has an override built in
<astearns> ack emilio
<myles> emilio: I see a point to override the color scheme. At the end of the day that's a user preference but it's not a hard preference. I'm not sure I agree overriding things like contrast-preference, reduced-motion, etc.
<ntim> +1 to emilio
<astearns> +1 to emilio
<myles> emilio: Those don't seem equally useful. Those are a11y preferences we expose so the page can react to that. There's no use case
<bramus> q+
<myles> TabAtkins: Remember these media queries are optional to pay attention to. The author can not look at them and do whatever they want. There is nothing that does a dang thing if the author doesn't want it. Nothing here reduces the ability for the user to communicate to the page or for the page to render different things. This is just for letting the user have a per-page toggle
<myles> TabAtkins: "This page is extra-wiggly" and let the user opt-into lower motion even if the user wouldn't have the global value set
<myles> emilio: Some other preferences (reduced-data) affect stuff like what headers the browsers send
<myles> emilio: there's no mention of that stuff
<myles> TabAtkins: Reduced-data isn't in the proposal
<myles> emilio: yeah it is
<myles> TabAtkins: i missed that
<myles> TabAtkins: i'm happy to iterate on this for what ones will apply
<myles> TabAtkins: That is not unreasonable bit of feedback
<myles> emilio: it feels sketchy to override prefers-reduced motion
<jasew1> Is there an option to keep this API shape but with reduced preferences? It seems we all agree about colorScheme for example but maybe not some others
<astearns> ack dbaron
<ntim> q+
<astearns> jasew1 yes, there is definitely time/space to iterate and refine
<myles> dbaron: it seems like with a bunch of these things, there's potentially a user-agent is doing other things with these things. things like prefers-reduced-motion, the UA does things that are not queryable.
<emilio> q+
<myles> dbaron: one thing that should be clear in the proposal is "is this about only affecting the reflection of these preferences to the site, or is this also about affecting other things"
<myles> dbaron: A) if it's just about reflection of the preferences to the site, there isn't a problem with things that use data. it just doesn't have the other effecets. But if it IS about changing other things, thn it's more complicated
<myles> TabAtkins: we've discussed these things, like automatically reducing motion if the prefence is enabled.
<vmpstr> q+
<fremy> q+
<myles> TabAtkins: I thought we were not going to do that, or reflect it in a forced media query
<dbaron> s/things that use data/prefers-reduced-data/
<jasew1> q+
<bramus> q-
<myles> myles: My recollection is that we decided not to automatically try to reduce motion
<myles> dbaron: There may actually be value for the site to provide a setting that's easier to get to than the OS setting
<astearns> ack ntim
<myles> ntim: are the overrides persistent?
<myles> TabAtkins: They are persistent. next time you visit the page, they will be applied
<myles> ntim: if you clear storage, will that clear the override?
<myles> TabAtkins: That is a question that we should answer.
<myles> TabAtkins: it's an open question.
<myles> TabAtkins: By default, when there's no clearing going on, the user is going to want the same setting
<myles> ntim: i don't think we should make it persistent
<myles> q+
<myles> TabAtkins: if it's not persistent, you have to have a blocking script at the top of the page, and store it somewhere (in a cookie)
<nicole_> q+
<myles> TabAtkins: defeats most of the difficulty of doing this
<astearns> ack emilio
<astearns> ack vmpstr
<myles> vmpstr: i think defining exactly who can see this change is important. can UA style sheets see this?
<emilio> q+ to say that reduced-motion has various side effects in Firefox
<myles> vmpstr: i'm questioning the persistence. How you define this ... <missed>
<myles> vmpstr: there needs to be some signal to say "yes ... <missed>"
<myles> TabAtkins: We should give a consistent answer for what value a media query's answer is, regardless of what's querying it. otherwise it's weird.
<myles> TabAtkins: For iframes, exactly what the passdown behavior is needs to be iterated on. But if you already have a communicatino channel (or if there isn't one), then we pass the preference down. otherwise, it sees the user's origial preference
<astearns> ack fantasai
<Zakim> fantasai, you wanted to react to vmpstr to comment that UA stylesheets should be reflecting color-scheme not prefers-color-scheme
<myles> TabAtkins: Goal: Don't open new communication channels, but respect the ones that exist
<myles> fantasai: i don't think there are places in the UA stylesheet where we are or should be making it conditional on prefers-color-scheme. Rather we should be doing it based on the color-scheme property
<myles> TabAtkins: Yeah but the other prefers-* are still fair game
<astearns> ack fremy
<myles> TabAtkins: a UA animation could conditionalize on prefers-reduced-motion
<myles> fremy: i have 2 comments
<vmpstr> s/this ... <missed>/what "the page is", if the page changes that there is a dark mode, it persists forever/
<myles> fremy: 1. I am of the opinion that if a user wants to use a dark mode on a website, they should communicate that to the UA
<ntim> +1 to fremy
<myles> +1 to fremy
<vmpstr> s/yes ... <missed>/I'm still dark mode/
<myles> fremy: If you want ot have a UA in chrome that applies dark mode to different pages, do it
<myles> fremy: I'd prefer that
<myles> fremy: If the website changes, and don't provide the feature any more, how is the user going to get the behavior any more?
<astearns> q?
<astearns> zakim, close queue
<Zakim> ok, astearns, the speaker queue is closed
<myles> fremy: It would make more sense. It's fine if there's an API the site can poll to popup the browser's UI or something, but at least as the user you are basically interacting with the user agent. that makes more sense.
<myles> fremy: Another way of doing this is to add a custom media query. If you don't want to say this is a UA behavior, why not just create a custom media query whose value is based on the OS's MQ, but you can change it
<myles> fremy: But we should make the UA in control of this.
<myles> TabAtkins: Regarding to UA: No user-agent has seen fit to stuff it into the UI. I don't want to rely on that being a thing they do. I suspect they won't want to. (Maybe i'm wrong.) But i don't think browsers are interested in offering more UI to expose preference toggles. In the absence of that, letting pages control it (which they do already) is straightforward and easy.
<myles> TabAtkins: For the persistence issue: There will be a way to clear the preference. It's an open question exactly which question. Please stop assuming that
<myles> TabAtkins: custom media query: MQs aren't suitable for this. It's awkward to do in the first place. There are mutliple features of the web that rely on various media queries that wouldn't work by default. 3rd party content wouldn't know to pay attention to your custom dark mode MQ. Manipulating the real MQ works more interoperably
<myles> jasew1: I've hearing concerns of preferences exposed and persistence.
<myles> jasew1: We can discuss outside of this. Assuming we can continue to discuss which preferences go into this, are there concerns with the API shape itself? Assuming we can make changes with the overall API
<myles> fremy: What I just mentioned. The UA should be more involved.
<fremy> @tabatkins: If there is a way for the user to make this "non-persistent", how do you tell the user that?
<myles> ntim: Persistence is a big one
<emilio> myles: I wanted to jump on the bandwagon and say that persistence is super scary
<emilio> ... and also agree with ntim and fremy that the UA should be in charge of this
<astearns> ack nicole_
<astearns> ack jasew1
<astearns> ack myles
<TabAtkins> The same way users know to clear *any* page-specific data. "clear cookies" is a bit advanced but not unknown
<astearns> ack jasew
<fremy> q+
<myles> nicole_: I think there's a complicated relationship between the OS, UA, and site setting. That needs to be worked out to make sure the user's intent is happening
<myles> nicole_: To add to persistence, with a multi-page app, it will be hard if it's page by page. Maybe there's a way to do that, but i maybe haven't understood it
<myles> nicole_: Usually single page apps actually have multiple pages. Something about that could be weird where part is on wordpress and part is a react app ... figuring out how to make sure the user's intent was following through what the website owner determined as their ... all the sites that make up their actual site. I do wonder if it's complex enough that we need to have another mechanism if it was to be done this way for site authors actually need
<myles> to be able to say "all these pieces are one site and the setting should persist across across all of them"
<myles> nicole_: But I agree it's best done at a UA level
<fremy> fremy was on the queue to say that Chrome already had a page for "site settings" with tons of toggles, I don't see why a switch could not be provided for site-specific accessibility settings
<myles> TabAtkins: For the multi-page site, th question of "do we need control over the scope". My feedback was "this should just apply to the origin" We don't need to offer a toggle. But it was in the earlier version of the API. If needed, we could put it back. For all the new UI people, there browser developers
<myles> TabAtkins: Are y'all saying "y'all committing to adding browser UI to allow per-site preference changes"? If you think it's a good idea, but you don't want to do it, i'd like to proceed
<myles> myles: i cannot comment on future produces or releases
<myles> emilio: 1. some of these preferences... i want concrete examples. some preferences have side effects apart from MQs. reduce-motion markes marquees not scroll in firefox. Disables smooth scrolling. The API should be explicit about whether that's effected
<myles> emilio: For color-scheme, you already have a resolution to inherit used color scheme for SVG images (and maybe iframes).
<florian> q+
<myles> emilio: Browsers do expose per-sites settings for this, but not to the site, but they do for extensions. Firefox does have per-site setting override that extensions and users can use.
<ntim> This is what emilio is referring to: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/browserSettings/overrideContentColorScheme
<myles> emilio: I'm not sure how that should work together with the site ... the site being able to override the user decision both ath the UA level and extension level seems confusing. The user can tell the exttension to be dark ... there are like 4 different switches now can effect the page. The user has to figure out which is the one that actually is effecting the rendering
<myles> TabAtkins: SVGs - you're right. Already handled. For iframes, they should see the override
<myles> fantasai: We already resolved iframes inherit from color-scheme
<myles> TabAtkins: We still want to apply them when necessary
<myles> astearns: We are at time
<myles> astearns: We should take this as "hey, this as an introduction of an interesting proposal"
<myles> ACTION(fremy): Look at the proposal, see whether there's an issue to be filed
<TabAtkins> vmpstr, no, eTLD+1
<myles> ACTION(TabAtkins): Determine if prefers-reduce-data is handled
<myles> astearns: Anyone else, please comment in the issue
<myles> <general discussion about breakout sessions for unrelated topics>

@bramus
Copy link
Contributor

bramus commented Sep 15, 2023

Last year I wrote about this problem space and why authors need this on my blog.

To accompany that article, I also built an interactive prototype that shows how it would work:

  • Use one of the dropdowns to change the OS / Browser / Site level setting. These settings work as a waterfall.
  • Use the links in the page itself to change the Site level setting (these would call the proposed API)
  • Use the button in the UA chrome itself to change the Site level setting.

Try the prototype here: https://codepen.io/bramus/full/yLvbgxL

Notice how everything is nicely in sync:

  • Setting the value at the site level from within the page gets reflected in the UA’s settings (note how the button next to the address bar changes).
  • Changing the Site’s level via the UA-provided botton is immediately reflected on the page in the MQ.

(This UA provided button could be a setting pane somewhere. I added it to this prominent place for demonstration purposes)

By syncing this setting to the UA’s per-site settings – similiar to how you control access to camera, geolocation, etc – it can persist those settings in your profile and sync it across devices.

As noted in WICG/web-preferences-api#16 (comment), changing the value from within script should have the proper safeguards. E.g. the UA could ask for confirmation the first time a site wants to change the value, similar to how sharing your geolocation works.

@lukewarlow
Copy link
Member

lukewarlow commented Sep 15, 2023

I'll reply to comments inline here.

emilio: I'm not sure I agree overriding things like contrast-preference, reduced-motion, etc.
emilio: Those don't seem equally useful. Those are a11y preferences we expose so the page can react to that. There's no use case

I would disagree on the statement that there's no use case. For two reasons, I don't have an issue on the whole with motion but some sites really do overdo it. If they provided a way to disable them per-site (origin) then that would benefit me. Also it's common place for sites specifically ones focussed on accessibility to provide a high contrast theme. Currently this has to be done manually by swapping out the stylesheet. This API proposal would allow for that to just use the media query.

emilio: Some other preferences (reduced-data) affect stuff like what headers the browsers send
emilio: there's no mention of that stuff

It is intended that the header reflects the preference. I do mention user preference client hint headers but you are correct I should make it more explicit and include the Save-Data header.

it feels sketchy to override prefers-reduced motion

I'd be keen to here why you think this? If a site want's to be obnoxious and override it to no-preference well they can largely already do this by just ignoring the preference. Excepting the question of UA styles which they don't contro currentlyl so that is a valid concern.

Is there an option to keep this API shape but with reduced preferences? It seems we all agree about colorScheme for example but maybe not some others

Absolutely, I copied all the mq5 preferences by default but I'm more than happy to discuss removal of some. I do think that more than colorScheme are useful though.

There may actually be value for the site to provide a setting that's easier to get to than the OS setting

I think this is a key point I'd like to emphasise. There's some OSs that don't even offer certain preferences, for example Chrome and Firefox for Android only very very recently got the ability to honour a contrast preference. Due to platform limitations I ended up having to implement them using "High Contrast Text" setting which isn't a 1 : 1 match.

It's also worth bearing in mind that there's only 1 platform that allows a "(prefers-contrast: less)" state and that's Windows. Aside from Firefox's forced colors palette in the browser which does offer this capability on other platforms.
This API is intended to allow sites to offer a more comprehensive accessibility experience.

If you clear storage will that clear the override?

Yes, if you clear site data the override will be cleared. The exact clearing mechanism is obviously ultimately down to the UA but I don't see this as being much different from offering a reset preferences option like you get with permissions. On top of also being cleared when site data as a whole is cleared.

i don't think we should make it persistent

Why? That's largely the whole basis for the API. We can spec it to have the same bahaviour as any other user controlled storage for UAs that are concerned such as Safari's 7 day storage eviction. But part of the benefit is this could survive that. On the basis it's not really useful for tracking.

If it's not persistent you also get the flash of unstyled content issue if the OS theme and user preference don't match. e.g a flash of white as the page loads and the preference is set.

i think defining exactly who can see this change is important. can UA style sheets see this?

I completely agree and UA styles is a great example of where the spec needs clarification. My intention is that it would apply to UA styles (for a given origin). However, I'm unsure on the exact implementation problems that could come from that. It's worth being aware Chromium actually doesn't support media feature queries in UA styles at all currently.) See WICG/web-preferences-api#22

If the website changes, and don't provide the feature any more, how is the user going to get the behavior any more?

Resetting the preference overrides in the site settings of the browser, or clearing all site data depending on the UA's implementation (again I don't think the mechanism for clearing can be specced as it's UA dependent but I'm happy to put that the UA MUST provide a way to reset)

Another way of doing this is to add a custom media query.

Tab already answered this but just to emphasise custom media queries are not the answer. They don't support color-scheme changes, they don't work with third party content, they don't integrate with user preference client hint headers, they don't integrate with conditional resource loading (using the standard queries at least). They are a brilliant idea and I think a much needed addition to the platform but for the specifics of this they do not solve the problem.

I wanted to jump on the bandwagon and say that persistence is super scary

Again I'm keen to hear the reasoning for this? What "attacks" could persistence of this setting lead to. If it's purely about sites removing this functionality down the road and the user being stuck. While that's valid and I hope addressed by the fact UAs can clear this, it's worth noting that well sites can already completely ignore the user preferences so from the users perspective it's not all that different?

Are y'all saying "y'all committing to adding browser UI to allow per-site preference changes"? If you think it's a good idea, but you don't want to do it, i'd like to proceed

This is the key of my thoughts. If you're willing to commit to adding per site (or heck even a browser level toggle which some don't offer atm) settings then great. But until we all get that we're stuck with a rather poor status quo.

I really don't want this to become the password toggle button over again. To be completely frank put your money where you mouth is and implement this ability in the browser, or give us web developers the ability to do it ourselves.

Not to mention the fact that I highly doubt browser UI will stop sites doing their own for color scheme and being stuck in the same place as before.

I also don't think browser UI and this API need be mutually exclusive. It's perfectly plausible for the browser to offer UI that can sync with the site overrides. Like shown in Bramus' demo above.

  1. some of these preferences... i want concrete examples. some preferences have side effects apart from MQs. reduce-motion markes marquees not scroll in firefox. Disables smooth scrolling. The API should be explicit about whether that's effected

Completely valid I'll make an issue on the spec to address this, thanks for the specific on what needs looking at! Fwiw these should all be affected too. (again completely open to discussion) See WICG/web-preferences-api#24

emilio: Browsers do expose per-sites settings for this, but not to the site, but they do for extensions. Firefox does have per-site setting override that extensions and users can use.

Again thanks for bringing this to my attention I wasn't aware such a feature existed. Again I'll raise an issue to discuss which should take precedence. I don't actually have answer off the top of my head to this one. My gut says the precedence should be OS < Browser < Site < Extension but needs discussion. See WICG/web-preferences-api#25

@lukewarlow
Copy link
Member

As noted in WICG/web-preferences-api#16 (comment), changing the value from within script should have the proper safeguards. E.g. the UA could ask for confirmation the first time a site wants to change the value, similar to how sharing your geolocation works.

Yep the exact shape of the API has changed once I'll happily change it again to be async functions if we want permissions integrations etc :)

@lukewarlow
Copy link
Member

lukewarlow commented Oct 5, 2023

To update those following this issue, I've made two key changes that I hope will reduce concerns. The API now uses an async requestOverride function, to allow browsers more control of the process. "no-preference" values have also been removed from the initial version of the spec. So overrides can only opt-in rather than opt-out of accessibility features.

@lukewarlow
Copy link
Member

There's an initial implementation of the proposed API in chrome canary (requires a launch flag). Currently it doesn't persist the settings but I'm working on adding that.

I've also started working on a list of examples of sites with settings that could benefit from this API and specifically which preferences inside of it. WICG/web-preferences-api#29

@frivoal
Copy link
Collaborator

frivoal commented Oct 23, 2023

So overrides can only opt-in rather than opt-out of accessibility features.

I'm not sure I understand why this is helpful.

When switched on by the user/user-agent, the various prefers-* MQ do not turn on accessibility features supplied by the user-agent. They inform the site that the user has a preference, and it's up to the site to do something about it. By default, nothing happens. If the site wants to ignore the user's preference, they already can, simply by doing nothing.

If the site has some reason to think that the user's preference (or lack thereof) is different from what it's getting from the ua, It can chose to do something else, or to do nothing. We cannot change that. It doesn't matter whether that comes from something reasonable like a settings dialog, or unreasonable like the author thinking that everyone surely agrees with their personal tastes: it's up to the author to supply the feature, so if they think they should not, there's nothing we can do about it.

So, I don't think we're protecting the user from anything by preventing the site from opting out.

Note: There are cases where the browser may combine a prefers-* MQ with some behavior forcibly done by the browser, but that's a separate thing, and these overrides would have no influence on those. For example, User agents may consider setting prefers-reduced-data based on the same user or system preference as they use to set the Save-Data HTTP request header. But the Save-Data HTTP request header is not caused by the prefers-reduced-data MQ, and even if a site overrode that MQ, it would have no effect on the HTTP header.

@lukewarlow
Copy link
Member

lukewarlow commented Oct 23, 2023

The API proposal is to override the underlying preferences which would affect all those things not just the MQs. Hence the additional "protections" to try to alleviate concerns. See https://wicg.github.io/web-preferences-api/#sec-intro for details

@jasonwilliams
Copy link

jasonwilliams commented Nov 30, 2023

The overall API shape looks good to me, moving this to a web preferences API, that can operate per-origin, seems to solve many more problems that have been mentioned above. It does feel like the right place to solve this problem.

Some observations.

Fetching computed value?

It looks like this API is strictly for overrides so those wanting to offer a toggle would still need to do an initial check with matchMedia to get the current value. Like below:

let currentValue = null;

// We don't know what the initial value is so lets get it.
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
    currentValue = 'dark'
}

button.onClick(() => {
	const newVal = (currentValue === 'dark') ? 'light' : 'dark';
	navigator.preferences.colorScheme.requestOverride(newVal)
		.then(() => {
			 // The preference override was successful.
		})
		.catch((error) => {
			 // The preference override request was rejected.
		});
})

Something like this might be better

button.onClick(() => {
	const newVal = (navigator.preferences.colorScheme.value === 'dark') ? 'light' : 'dark';
	navigator.preferences.colorScheme.requestOverride(newVal)
		.then(() => {
			 // The preference override was successful.
		})
		.catch((error) => {
			 // The preference override request was rejected.
		});
})

It doesn't feel the most ergonomic.
I wonder if the API provides the computed value we could skip the first step entirely.
Looks like this is covered in: WICG/web-preferences-api#7

Although I don't fully understand how the API shape WICG/web-preferences-api#7 (comment) solves this so it would be good to see an example.

Should the fulfilled promise return the set value?

For those who are implementing a toggle, I wonder if the fulfilled promise should return the value that is now set, ergonomically this would make it easy for website authors to pass that value to their currentSet state.

iFrames

I guess there's questions on how to deal with iframes (if we even need to). But that's being discussed here:
WICG/web-preferences-api#8


I guess now that this has moved into a potential browser API and into the WICG, I guess its no longer a CSS WG matter, unless any here feel against the direction this is in.

If people are happy with the control of this being in Web Preferences, then @tabatkins I would be interested in your thoughts on where https://github.com/WICG/web-preferences-api goes in terms of reviewal, does this go into the Web Apps working group?

@lukewarlow
Copy link
Member

lukewarlow commented Nov 30, 2023

Although I don't fully understand how the API shape WICG/web-preferences-api#7 (comment) solves this so it would be good to see an example.

The proposal and your idea for improvement is basically the same only difference is I've done values as an array because technically they could match multiple values at once. This isn't very ergonomic though so I would rather just do .value and accept that these media queries are single value at a time.

button.onclick = () => {
	const newVal = (navigator.preferences.colorScheme.values[0] === 'dark') ? 'light' : 'dark';
	navigator.preferences.colorScheme.requestOverride(newVal)
		.then(() => {
			 // The preference override was successful.
		})
		.catch((error) => {
			 // The preference override request was rejected.
		});
};

My proposal is to make each of the preference objects an event target too so you can listen for changes in them. That way you don't need to do use match media.

@tabatkins
Copy link
Member Author

does this go into the Web Apps working group?

I think it's reasonable for this to graduate either to the CSSWG or the WebApps WG for standardization, with a slight preference for the CSSWG since it's so intimately connected to an existing CSS spec (Media Queries). We'd want to make sure any new preference MQs are reflected in this API, and it's a little easier to remember to do that when they live under the same WG.

@lukewarlow
Copy link
Member

Some updates on the web preferences API proposal.

It now has a .value property which addresses the ergonomics issue with needing matchMedia, the preference objects are now also event targets that fire a change event in various scenarios, this again is a big ergonomic win.

I feel like the spec and chromium prototype (which now should only be missing persistence) is in a state to progress this now.

Given the appetite for this to be in the csswg what's the best next steps?

@tabatkins
Copy link
Member Author

Agenda+ to discuss adopting this into a CSSWG deliverable. (Maybe just folding into MQ5?)

@lukewarlow
Copy link
Member

It would be good if this could make it to the Agenda for the face to face, given I'll be there to discuss it. Do I need a different label for that?

@tabatkins
Copy link
Member Author

Yup, tagged.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [mediaqueries-5] Script control of (prefers-*) queries, and agreed to the following:

  • RESOLVED: Put this proposal into css-mediaqueries-5
  • RESOLVED: Add lwarlow as an editor for mediaqueries-5
The full IRC log of that discussion <flackr> TabAtkins: we've discussed this before. For a quick recap, there are many cases for e.g. light mode or dark mode on an individual site that can be distinct from OS preference
<flackr> TabAtkins: right now, you have to manually store some bit in a cookie, doing styling based on that info with JS, possibly flashing the wrong style of content before that JS runs, in general it's relatively awkward to have something possibly depend on a media query or script info
<JaseW> Example: https://expressjs.com/ does this (Flash of unstyled content/theme while it figures out what you want)
<flackr> TabAtkins: this proposal is to allow a site to override what the OS / browser says the value of a MQ is, and a persistence mechanism to keep it until the site data is cleared or removed
<flackr> TabAtkins: the author can always ignore the MQ and do what they want, it just allows them to rely on the MQ and optionally override it without separate code paths
<flackr> TabAtkins: This seems to have overall support. Would we like to adopt this into a csswg deliverable?
<flackr> TabAtkins: Possibly css, possibly webapps. Given the tight connection to MQ likely css.
<florian> q+
<flackr> TabAtkins: It would likely go in css-media-queries-5
<astearns> ack florian
<lwarlow> q+
<flackr> florian: Based on how you described it, I'm supportive. In the issue some comments seem contradictory. E.g. "this api is to override the underlying perference affecting all the things"
<flackr> florian: does this affect the underlying thing as well as the MQ's? This seems confusing
<astearns> ack lwarlow
<ntim> p+
<flackr> lwarlow: as spec'd, the preference applies to the MQ, is used as calc for used color-scheme, used to override the user preference MQ client hints, that's the 3 main things. It is largely the MQ.
<flackr> lwarlow: the used color-scheme seems reasonable as part of that
<flackr> florian: is this different from what TabAtkins said?
<khush> q+
<flackr> lwarlow: yes, slightly different. it is only affecting the MQ and not changing anything you can't ignore already
<flackr> TabAtkins: right, keeps things consistent across these apis
<astearns> ack khush
<flackr> khush: 2 questions. Are you thinking of exposing this for all MQ's, or an allowlist that is expected to be overridden? E.g. maybe prefers-reduce-motion doesn't make sense?
<flackr> khush: Are there cases for closed shadow roots or UA shadow dom where authors can now override things they couldn't before?
<flackr> lwarlow: The list of MQ's supported are the 5 pref ones, color-scheme, contrast, reduce-motion, reduce-transparency and reduce-data
<flackr> lwarlow: I've done some research to sites in the wild. Reduce motion comes up often, so for the same reasons as color-scheme i think this makes sense
<JaseW> q+
<flackr> lwarlow: to the second point, yes currently there could be user-agent styles using MQ's that would be affected. Off the top of my head I'm not aware of any though.
<flackr> lwarlow: I think this fits into the premise of the API but it is a slightly new capability
<flackr> TabAtkins: I think it's entirely a good thing that this works, to keep things consistent to having the MQ pref set in the browser
<flackr> TabAtkins: right now, script controlled functionality doesn't do this resulting in inconsistent behavior
<flackr> TabAtkins: this makes everything behave as expected
<flackr> khush: sounds reasonable
<astearns> ack JaseW
<flackr> JaseW: A question for implementers, how would you feel if a site requests an override for color-scheme or reduce-motion? Would you expect this to trigger a permission prompt? We've already mentioned web devs can ignore, so how do we feel about this?
<flackr> JaseW: do you expect some UI alongside this? e.g. allowing users to change the value through the browser
<flackr> TabAtkins: This has been discussed. Because it's no additional functionality, it doesn't need a permission prompt
<flackr> TabAtkins: for resetting something, this should be tied to site data. So you can clear site data and clear these prefs
<flackr> TabAtkins: UA's could offer more granular controls but it would at least be recommended to reset with site data
<ntim> q+
<astearns> ack ntim
<flackr> ntim: I'm concerned that this is persistent. Let's say the site changes owner, the override stays on that domain. This seems wrong. Whereas persisted by cookies the new site wouldn't use the cookie
<lwarlow> q+
<flackr> ntim: The user doesn't have an obvious way to reset this
<flackr> TabAtkins: worst case a user can reset site data, but otherwise hopefully the web dev would clear this
<JaseW> q+
<astearns> ack lwarlow
<flackr> lwarlow: you can already design your site to ignore the user pref
<flackr> lwarlow: the idea is you'd probably have a way in the UA to clear these independent of clearing cookies but on the spec side wouldn't be required to have
<flackr> lwarlow: the fact that it's persistent is a powerful part of this API, to avoid a flash of incorrectly styled content, JS at every load to restore, etc
<flackr> lwarlow: I see the issue, but given good browser UI and existing problem, it doesn't seem too concerning
<khush> q+
<astearns> ack JaseW
<khush> q-
<flackr> JaseW: is there not already a concept of per-site settings based on the origin rather than cookies?
<flackr> JaseW: seems like this could be part of that
<ntim> q+
<astearns> ack ntim
<flackr> ntim: does that mean the api will need to be async? if the browser can provide UI to disable / override it
<flackr> lwarlow: the api is designed so the request override is an async function
<flackr> lwarlow: i don't think requesting the override generally requires intervnetion but it could be
<flackr> q+
<flackr> astearns: are there many open issues on the repo to be addressed?
<khush> q+
<flackr> lwarlow: there's one or two, how this interfaces with third party iframes. I think the answers already exist in various specs and just need to relay that information
<flackr> lwarlow: mainly ensuring this doesn't introduce a new communication channel
<flackr> lwarlow: also does it make sense to have all of these supported? based on research it seems the answer is yes
<astearns> ack flackr
<TabAtkins> flackr: I think it would be good if hte UA was allowed to set as well as clear them
<JaseW> q+
<TabAtkins> flackr: UA could decide it wants a darkmode per-site toggle, could be useful to do
<flackr> lwarlow: i agree, this is the intent that it coudl be set by the UA
<astearns> ack khush
<flackr> khush: was going to say the same thing as flackr, it seems better if this were browser ui, then we don't have to force each website to implmement a pattern of adding a button
<miriam> q+
<flackr> khush: if this were to exist would we still want this?
<flackr> lwarlow: if that existed, it'd be great, but i still think at least color-scheme and possibly others you still have a use case where you want to sync values across accounts (e.g. log in on a diff browser), and even aside from this there are enough sites in the wild that have existing dark mode toggles that would be nice to integrate
<flackr> lwarlow: with just the browser ui, some sites would probably still have their own setting
<keithamus> q+
<flackr> flackr: I agree
<astearns> ack JaseW
<astearns> ack miriam
<flackr> miriam: I also support this view. It would be amazing to have browser UI for this, but sites will still want it as part of their interface
<astearns> ack keithamus
<ntim> q+
<flackr> keithamus: light and dark are fine, but for artistic styles there's no way to enumerate these to the UA other than inferred from the stylesheets
<flackr> keithamus: there's an idea to add a custom-ident, would the API allow overriding this?
<flackr> lwarlow: in its current form no, but it could be extended to set this
<flackr> lwarlow: you'd basically be able to build github's preferences, high contrast, dark mode, etc
<khush> that's a good point.
<astearns> ack ntim
<flackr> ntim: I'd have a concern about custom idents if the site doesn't have a way to enumerate them
<flackr> ntim: if we were to show browser ui, we should be able to enumerate what's supported vs what's not
<ydaniv> q+
<flackr> ntim: i think this should be limited to a certain set of MQs. I don't think the site should be able to override forced colors for example, or other AX related queries
<flackr> TabAtkins: right, only the prefers ones rather than ones that are ignorable / preference
<astearns> ack ydaniv
<flackr> ydaniv: speaking for a design tool, it'd be good to have the API so that you could check how your site looks with different schemes / MQ values
<flackr> astearns: I'm hearing concerns for particular bits of this API, which MQs it should apply to what values it supports, this can be hashed out in our draft but it sounds like we have rough consensus to add it to mq 5
<flackr> astearns: any objections to putting this in MQ5?
<flackr> RESOLVED: Put this proposal into css-mediaqueries-5
<flackr> TabAtkins: can we add lwarlow as an editor?
<flackr> lwarlow: I'm happy to
<flackr> RESOLVED: Add lwarlow as an editor for mediaqueries-5
<flackr> astearns: can you migrate issues over as needed?
<flackr> lwarlow: yes, will do
<ChrisL> Yay another NJTF editor!
<flackr> astearns: please open issues about custom idents, what MQs this applies to and the issues raised here
<ChrisL> * Not Just Tab & Fantasai
<TabAtkins> +1 to njtf
<flackr> astearns: anything more on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Unsorted
TPAC 2023 agenda
Friday Morning
Development

No branches or pull requests