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

Add support for defining a theme color for both light & dark modes (prefers color scheme) #975

Open
jensimmons opened this issue May 4, 2021 · 94 comments

Comments

@jensimmons
Copy link

jensimmons commented May 4, 2021

Currently, in a web manifest file, you can define a theme color.
https://www.w3.org/TR/appmanifest/#theme_color-member
For example:

{
    "lang": "en",
    "short_name": "Resilience",
    "name": "Resilient Web Design by Jeremy Keith",
    "description": "A web book in seven chapters on the past, present, and future of web design. By Jeremy Keith.",
    "background_color": "#5f7995",
    "theme_color": "#5f7995"
}

There's not currently any way to express that a certain theme color should be used for light mode, while another is used for dark mode.

In HTML, there's currently a proposal to add support for color schemes by adding a media attribute to the meta tag:
whatwg/html#6495

<meta name="theme-color" media="(prefers-color-scheme: light)" content="red">
<meta name="theme-color" media="(prefers-color-scheme: dark)"  content="darkred">

It would be great to be able to do a similar thing in the web manifest file.

@malchata
Copy link

malchata commented May 4, 2021

One approach that sounds good to me would be to overload "theme_color" to accept either a string (the current behavior), or a nested object like so:

"theme_color": {
  "dark": "#000000",
  "light": "#ffffff"
}

Not sure if this approach conflicts with existing functionality in any way, but it's the first approach that came to mind.

@dcrousso
Copy link
Member

dcrousso commented May 4, 2021

I don't think we can touch "theme_color" as that will break older browsers.

We may want to create a "theme_colors" (plural) property that is an array of objects (so that it's extensible if needed in the future) that would take precedence over "theme_color"

"theme_color": "red",
"theme_colors": [
    { "color": "red" },
    { "color": "darkred", "media": "(prefers-color-scheme: dark)" }
]

I don't have a strong preference, but I think using the last item in the list that matches (i.e. last pass) is generally easier to understand/follow (it also more closely matches the way it would be written in CSS).

If this is an approach we like, perhaps we could/should do it for "background_color" (i.e. "background_colors") too?

@malchata
Copy link

malchata commented May 4, 2021

I'm in favor of this approach. It leaves existing functionality where it is, and extends the web manifest in a way that allows for existing or future media values should they become applicable in this scenario.

@mirisuzanne
Copy link

That approach makes sense to me as well – although I might think of it as a default/shorthand, and longhand list of alternates, rather than new and legacy values. That would allow a simpler combination in the short term:

"theme_color": "red",
"theme_colors": [
    { "color": "darkred", "media": "(prefers-color-scheme: dark)" }
]

Authors could also choose to provide only the default:

"theme_color": "red",

Or (down the road) move to only using the longhand:

"theme_colors": [
    { "color": "red" },
    { "color": "darkred", "media": "(prefers-color-scheme: dark)" }
]

@tomayac
Copy link
Contributor

tomayac commented May 5, 2021

Also see w3c/image-resource#26 where the same question is discussed in the context of ImageResource as used by Web Application Manifest.

@mgiuca
Copy link
Collaborator

mgiuca commented May 6, 2021

+1, see my comment on w3c/image-resource#26 where I said "sure it would be great for ImageResources, but we'd also want it for the theme color". @aarongustafson FYI.

We (Google) are thinking about implementing something like this in the near future (~N quarters) so having agreement on the exact format of this field in the manifest would be great.

@mirisuzanne 's proposal sounds good to me. The problem it's trying to solve (not breaking backwards compat with the existing theme_color spec) has echoes of display-override and the solution is similar (have a separate field that overrides the existing field's values with more nuanced information).

@aarongustafson
Copy link
Collaborator

aarongustafson commented May 7, 2021

Thanks for filing this @jensimmons, It’s been on my backlog for a few weeks.

We discussed this over in #955 as well. I think the piece that’s missing from the above suggestions is that multiple members may need to change based on the color scheme. My original thought was that we introduce a color_schemes member which supports multiple color schemes as defined by CSS. That way it stay in lock-step with that spec as it continues to evolve. So, for example:

"color_schemes": {
  "dark": {
    "theme_color": "#fff",
    "background_color": "#000"
  },
  "light": {
    "theme_color": "#000",
    "background_color": "#fff"
  }
}

Of course there’s also prefers_contrast and forced_colors, which we may want to consider in any solution. Perhaps, combining this idea with @mirisuzanne’s:

"color_overrides": {
  "(prefers-color-scheme: dark)": {
    "theme_color": "#fff",
    "background_color": "#000"
  },
  "(prefers-color-scheme: light)": {
    "theme_color": "#000",
    "background_color": "#fff"
  }
}

or

"color_overrides": [
  {
    "media": "(prefers-color-scheme: dark)",
    "theme_color": "#fff",
    "background_color": "#000"
  },
  {
    "media": "(prefers-color-scheme: light)",
    "theme_color": "#000",
    "background_color": "#fff"
  }
]

In terms of processing, I’m not sure which would be faster to compute. We’d probably also want the first match to win if there are multiple matches.

Thoughts?

@mirisuzanne
Copy link

I like your first combined syntax, with media-conditions as keys, with nested objects. If we were matching the way CSS works, I would expect last-take-precedence when there are multiple matches - but maybe first-takes-precedence fits the existing scheme better? I don't know.

@aarongustafson
Copy link
Collaborator

I like your first combined syntax, with media-conditions as keys, with nested objects.

That’s my preference as well. Not knowing the innards of browser engines, it also feels like it would be faster to process because you wouldn’t have to parse the object to determine whether you need to throw it away.

If we were matching the way CSS works, I would expect last-take-precedence when there are multiple matches - but maybe first-takes-precedence fits the existing scheme better? I don't know.

This is the confusing piece, I agree. So we have 2 conflicting paths for this stuff: In CSS proximity wins whereas in things like audio, video, and picture the first match wins (even with media in play). Again, not knowing the browser innards well and going purely by the processing algorithms we define in the spec, I believe the first match winning is less computationally expensive overall, which is why I tend to lean in that direction.

I’d love someone with knowledge of the perf cost to weigh in here and set me straight if I’m incorrect.

@conde2
Copy link

conde2 commented May 12, 2021

"color_overrides": [
  {
    "media": "(prefers-color-scheme: dark)",
    "theme_color": "#fff",
    "background_color": "#000"
  },
  {
    "media": "(prefers-color-scheme: light)",
    "theme_color": "#000",
    "background_color": "#fff"
  }
]

1+ for this one, would like to see this in webmanifest

@jensimmons
Copy link
Author

Re: adding theme_colors to live alongside theme_color, and perhaps extending that idea to add background_colors to live alongside background_color...

This is the kind of thing the CSSWG always avoids. It's very confusing for developers to have remember whether or not there's an 's' at the end — and that the version with the 's' works differently than the one without. This is especially true for folks who don't speak English fluently or at all, which is an incredible number of developers.

An entirely different name is much better, like color_schemes or color_overrides. This is something that people can learn & understand completely separate from the original mechanism for theme color... and eventually let theme_color fade away as something the industry ignores when teaching or doing web development in the future.

@aarongustafson
Copy link
Collaborator

100% agree with @jensimmons on the naming stuff above.

Could @mgiuca @marcoscaceres @tomayac @rayankans and/or anyone else who works on internals weigh in on any perf differences between the options I outlined above.

@marcoscaceres
Copy link
Member

I can't see any perf differences, but I'd be inclined to go with:

"color_overrides": [
  {
    "media": "(prefers-color-scheme: dark)",
    "theme_color": "#fff",
    "background_color": "#000"
  },
  {
    "media": "(prefers-color-scheme: light)",
    "theme_color": "#000",
    "background_color": "#fff"
  }
]

Just from an object creation POV. I'm personally not a huge fan of property names having a dual role.

However, we will need to work out what to do when multiple media queries/features match. Last wins? first wins? Most specific wins?

@marcoscaceres
Copy link
Member

Hmmm..., we'd probably want to just generalize the overrides... and now I've changed my opinion about the property names, as it's clear what it's doing...

"media_overrides": {
  "(prefers-color-scheme: dark)": {
    "theme_color": "#fff",
    "background_color": "#000"
  },
  "(prefers-color-scheme: light)": {
    "theme_color": "#000",
    "background_color": "#fff"
  }
}

@tomayac
Copy link
Contributor

tomayac commented Jun 1, 2021

I like the elegancy of the above proposal and move my thumbs up to this:

"media_overrides": {
  "(prefers-color-scheme: dark)": {
    "theme_color": "#fff",
    "background_color": "#000"
  },
  "(prefers-color-scheme: light)": {
    "theme_color": "#000",
    "background_color": "#fff"
  }
}

The approach feels very familiar to how you'd do this in CSS:

@media (prefers-color-scheme: dark) {
  /* … */
}

As an analogy, for processing <link media>, the spec simply says the resource must be applied if the media query matches. In case of multiple matches, I'd vouch for using the last matching one.

@marcoscaceres
Copy link
Member

marcoscaceres commented Jun 1, 2021

Half joking... we could apply them in order:

{
"background_color": "white",
"media_overrides": {
  "all": {
    "background_color": "green",
    "name": "lolz"
  },
  "(prefers-color-scheme: light)": {
    "background_color": "blue",
    "theme_color": "#000"
  }
}
}

Would:

  1. always override "background_color": "green",
  2. Add "name", because why not...
  3. add "theme_color": "#000" iff (prefers-color-scheme: light)

🧐

@tomayac
Copy link
Contributor

tomayac commented Jun 1, 2021

With you on the…

  1. add "theme_color": "#000" iff (prefers-color-scheme: light)

…part.

Not sure what the difference of the "media": "all" part would be for "background-color" compared to just the regular "background-color". I mean, yes, you could do that I guess…

For the "name" in there, I think we should limit the allowed children of an "@media block" (we need a name for this) and limit it somehow to just visible things.

@marcoscaceres
Copy link
Member

For the "name" in there, I think we should limit the allowed children of an "@media block" (we need a name for this) and limit it somehow to just visible things.

Yes, we could define a list props that it applies to, and only override those (ignore the rest).

@marcoscaceres
Copy link
Member

Ok, so concrete proposal is now "media_overrides", whose member names are a valid <media-query-list> production of Media Queries. For each declared property, the members are limited to:

  • theme_color
  • background_color

Rough processing:

For each "query" of get own property names "media_overrides":

  1. If query is not a valid MQ, continue.
  2. Evaluate query. If media matches, use the property value (by re-processing it to make sure it's valid).

That should provide the maximum flexibility.

Thoughts?

@tomayac
Copy link
Contributor

tomayac commented Jun 1, 2021

For "icons", which we deal with in w3c/image-resource#26, it seems like @aarongustafson's proposal is the latest and greatest.

It would be great to align the two proposals. From what I can tell, ImageResource icons are used in the "icons" member, but also nested in the "shortcuts" member.

Combined, this would look like in the example below. There's some duplication with the "shortcuts", but I think it's tolerable.

{
	"media_overrides": {

		"(prefers-color-scheme: dark)": {
			"theme_color": "#fff",
			"background_color": "#000",

			"icons": [{
				"src": "/icons/icon-dark.png",
				"sizes": "192x192"
			}],

			"shortcuts": [{
				"name": "Play",
				"short_name": "Play",
				"description": "Play the list of podcasts",
				"url": "/play?utm_source=homescreen",
				"icons": [{
					"src": "/icons/play-dark.png",
					"sizes": "192x192"
				}]
			}]
		},

		"(prefers-color-scheme: light)": {
			"theme_color": "#000",
			"background_color": "#fff",

			"icons": [{
				"src": "/icons/icon-light.png",
				"sizes": "192x192"
			}],

			"shortcuts": [{
				"name": "Play",
				"short_name": "Play",
				"description": "Play the list of podcasts",
				"url": "/play?utm_source=homescreen",
				"icons": [{
					"src": "/icons/play-light.png",
					"sizes": "192x192"
				}]
			}]
		}
	}
}

@aarongustafson
Copy link
Collaborator

This is great folks! I agree that I like the syntax better where the key is the MQ. Should I start a PR on this?

For "icons", which we deal with in w3c/image-resource#26, it seems like @aarongustafson's proposal is the latest and greatest.

Yes, I have it tracked in a bug against ImageResource as well. Ideally, it’d be great to have the ImageResource support color prefs so as not to create the kind of complicated duplication you mentioned, @tomayac. But if folks need to override separately, it’d be there as a fallback.

Just to cover all the bases, I’d suggest we try to land the ImageResource one first so the Manifest spec can reference it.

@dcrousso
Copy link
Member

dcrousso commented Jun 1, 2021

While I don't have a personal preference over how this is structured, I would give some caution over using <media-query-list> as property keys instead of as the value of a "media" property inside the object as the former basically prevents any combination with some other form of "only apply this when X" system that could get added in the future. I would be somewhat surprised if something like that was added, but I also can't deny it as a possibility and would hate to introduce yet another "*_overrides" and have to figure out a precedence/combination logic if that does happen.

@tomayac
Copy link
Contributor

tomayac commented Jun 1, 2021

Trying to speak for developers, I think consistency is key here. I don’t think it’s a good experience for developers having to remember that for ImageResource it’s a "media" property they need to set and for "media_override" the media query is the actual key. So I’d rather go for using a "media" property throughout. Sorry for the back-and-forth here.

Another point that I realize now: If folks create their manifests programmatically, it’s also more convenient to be able to foo.media rather than having to use foo['(prefers-foo: bar)'].

@jensimmons
Copy link
Author

Yeah, hot take — I’m not sure what media_override means. Overriding? What am I overriding? Isn't all my code technically an override of previously-parsed code?

Instead, media matches what I’d write in HTML and in CSS. The more we can make the code similar in all three places, the better.

<meta name="theme-color" media="(prefers-color-scheme: light)" content="red">
<meta name="theme-color" media="(prefers-color-scheme: dark)"  content="darkred">
@media (prefers-color-scheme: dark) {
  img {
    filter: brightness(.8) contrast(1.2);
  }
}
"media": {
  "(prefers-color-scheme: dark)": {
    "theme_color": "#fff",
    "background_color": "#000"
  },
  "(prefers-color-scheme: light)": {
    "theme_color": "#000",
    "background_color": "#fff"
  }
}

@dcrousso
Copy link
Member

dcrousso commented Jun 2, 2021

from my earlier comment

basically prevents any combination with some other form of "only apply this when X" system that could get added in the future

this actually exists right now in @supports. Using a <media-query-list> as the key means that @supports wouldn't be allowed. Especially since we're dealing with CSS colors I think it'd perhaps be useful for a developer to be able to do something along the lines of

{
    "conditions": ["@media (prefers-color-scheme: dark)", "@supports (color: color(display-p3 1 1 1 1))"],
    "theme_color": "color(display-p3 1 1 1 1)",
    ...
}

(i.e. check that the exact color they want to use is supported before attempting to use it)

@marcoscaceres
Copy link
Member

@jensimmons... great points about matchings css/html! and I know I've misspelled "override" a lot 😅, so the shorter "media" is nice for that reason too!

@dcrousso, I think that's pushing it a bit beyond the use case. Worst case, you can do the supports checks in JS, then instruct either the service worker or server to respond with the particular color syntax. I'd prefer we keep things simple unless there is strong case for checking @supports.

@aarongustafson, let's get a all the straw-person in place and engine buy-in (or no objections!) before we proceed with spec'ing it.

If Mozilla supports adding this, I'd be happy to implement it in Gecko. @annevk or @tantek, are either of you the right people to ask about this? Or is this something we ned to get a standards position on? Appreciate your guidance.

@jensimmons, @hober, similar question. We know Apple doesn't comment on future products or implementations, but would you object to this being added to the spec?

Any further refinements appreciated. I think we are still unclear if we should process all the matches, or if first or last wins.

@annevk
Copy link
Member

annevk commented Jun 2, 2021

Something like this seems reasonable to me and in line with the arguments put forward in mozilla/standards-positions#500 against multiple manifests (some restating much older arguments dating back to the original design of manifests). It seems small enough to me to not warrant a position.

(Thanks in advance for the patch!)

@marcoscaceres
Copy link
Member

@tomayac wrote:

There's some duplication with the "shortcuts", but I think it's tolerable

I'm not liking the duplication of shortcuts, TBH. I wonder if we can do better there... it looks like image resources should sprout at media member?

{"shortcuts": [
    {
        "name": "Play",
        "short_name": "Play",
        "description": "Play the list of podcasts",
        "url": "/play?utm_source=homescreen",
        "icons": [
            {
                "media": "(prefers-color-scheme: light)",
                "src": "/icons/play-light.png",
                "sizes": "192x192"
            },
            {
                "media": "(prefers-color-scheme: dark)",
                "src": "/icons/play-dark.png",
                "sizes": "192x192"
            }
        ]
    }
]}

@marcoscaceres
Copy link
Member

marcoscaceres commented Jun 10, 2022

As an aside, we should bring back whatwg/html#6444 ...

@FluorescentHallucinogen, you are not wrong... and I agree that should be supported (HTML spec issue).

However, we are going somewhat for developer convenience here. I think it does get a little unmanageable if you have to keep multiple manifest for both hreflang and media. For example:

<link
  rel="manifest"
  href="./fr/manifest.dark.json"
  media="(prefers-color-scheme: dark)"
  hreflang="fr"
/>

<link
  rel="manifest"
  href="./en/manifest.dark.json"
  media="(prefers-color-scheme: dark)"
  hreflang="en"
/>

And so on...

FWIW, I'm not sold on override object... I'd prefer just to keep a flat structure (at least, I'm not seeing any added value).

{
  "name": "Good dog", 
 "translations": {
    "fr": {
      "name": "Bon chien"
    }
  },
  "user_preferences": {
    "color_scheme_dark": {
      "icons": []
    }
  }
}

In terms or processing, "translations" should probably apply first, followed by "user_preferences". Having said that, we need to consider if certain things shouldn't be affected by user_preferences (e.g., the name and short_name). Similarly, an icon of the shortcut might change, but changing the shortcut's name would be weird.

It would also be strange if new icons/shortcuts appeared also in dark mode. So we would need to account for that.

@aarongustafson
Copy link
Collaborator

In terms of processing, "translations" should probably apply first, followed by "user_preferences". Having said that, we need to consider if certain things shouldn't be affected by user_preferences (e.g., the name and short_name). Similarly, an icon of the shortcut might change, but changing the shortcut's name would be weird.

@marcoscaceres In our proposals for user_preferences and translations we account for this, allowing for only specific properties of complex objects to be redefined in specific contexts. I actually just took a stab at walking through the complexities in the draft PR @loubrett and I are working on for user_preferences.

@immortalcodes
Copy link

Hi , I came here looking for a way to add this feature in my application, so till you have a proper fix , is there a temporary way to achieve this feature for defining a theme color for both light & dark modes

@marcoscaceres
Copy link
Member

marcoscaceres commented Jun 23, 2022

@immortalcodes, see #975 (comment)

(i.e., right now, you need two or more manifest ... but not all (any?) browsers support media="" on <link rel=manifest>... give it a try though and let us know!)

@marcoscaceres
Copy link
Member

marcoscaceres commented Jun 24, 2022

@aarongustafson, discussed this internally, and we'd like to propose going back to the media queries syntax. The rationale being that, at install time, the browser should take the possible MQ user preference values and convert them statically into something the OS understands (XML or whatever).

As we discussed, another issue was that we (well, me... my fault 😬) had proposed essentially creating a new query/matching language for this. Feedback I got was, "yeah... cool idea... but please don't do that"... which is fair enough.

The one thing we do need to do, however, is talk to the CSS working group to see if we can solve "translations" as a user preference. Personally, I think that makes sense, as it's literally a system user preference, just like dark/light mode is. We would need to bake that into a media syntax somehow ("prefers-languages:" media feature or something).

Also, we are leaning strongly towards simple overrides for matches (even if they are more verbose). That is, if you declare an override, and it matches, that is what comes out at the end (even if the set of items in the override is not equal to what is declared in the root).

@aarongustafson
Copy link
Collaborator

Thanks for this @marcoscaceres! I’d like to suggest we take a step back and come up with a set of principles to guide us in this effort. I have taken a stab at one such set over on the user_preferences discussion.

FWIW, I think the prefers-language/prefers-languages media feature is interesting. I do wonder what use case(s) it has, however. It seems like the Accept-Language header is a much more valuable signal and would allow for better performance through server-side negotiation rather than handling things at the layout & rendering layer.

@marcoscaceres
Copy link
Member

marcoscaceres commented Jun 28, 2022

FWIW, I think the prefers-language/prefers-languages media feature is interesting. I do wonder what use case(s) it has, however.

I was thinking:

  1. User browses the app in English and does not log in. For privacy reasons, the UA lies to the web page about the Accept-Language (always "en-US").
  2. User decides to install the app.
  3. The install prompt is presented and it is localized to match the actual OS preferred language.

It seems like the Accept-Language header is a much more valuable signal and would allow for better performance through server-side negotiation rather than handling things at the layout & rendering layer.

Right, but this signal is for use outside of the browsing context. Like with changing icons based on prefers-color-scheme and icons, it might also change dynamically if the user changes their OS (or app) language from system preferences.

@marcoscaceres
Copy link
Member

Hi all, last week I took an action to consolidate all the solutions into a single proposal so we can evaluate the pros and cons and get a better understanding of what we are trying to do here. The result is at: #1045

Please take time to consider what is proposed there - there are a lot of "sharp edges and dark corners"! 👻

After evaluating all the different approaches, my conclusion was that building on @dcrousso's #975 (comment) proposal makes the most sense.

Would like to hear your thoughts, and if we agree, we can draft something up.

mjfroman pushed a commit to mjfroman/moz-libwebrtc-third-party that referenced this issue Oct 14, 2022
Parses the user_preferences field from the manifest and sets
dark_mode_theme_color and dark_mode_background_color in
WebApplicationInfo.

This is based off the proposal in this comment:
w3c/manifest#975 (comment)
There is no explainer yet.

This is the same format as the translations member.

Intent to prototype:
https://groups.google.com/a/chromium.org/g/blink-dev/c/Y6zNtG0f-6A

Bug: 1271804
Change-Id: I9764f300159d35041323d4a1602e06ba7f80afd8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3292905
Commit-Queue: Louise Brett <[email protected]>
Reviewed-by: Kentaro Hara <[email protected]>
Reviewed-by: Dominick Ng <[email protected]>
Cr-Commit-Position: refs/heads/main@{#945184}
NOKEYCHECK=True
GitOrigin-RevId: b076313fcbacd0be2f2b663f7d16083a19555e07
@Akshat162001
Copy link

This approach makes sense to me –

"theme_color": "red",
"theme_colors": [
{ "color": "darkred", "media": "(prefers-color-scheme: dark)" }
]

Authors could also choose to provide only the default:

"theme_color": "red",

Or (down the road) move to only using the longhand:

"theme_colors": [
{ "color": "red" },
{ "color": "darkred", "media": "(prefers-color-scheme: dark)" }
]

Hope it would work...

@monecchi
Copy link

Hi there! Any resolution regarding the proposed support? I really wanted to try it out in my pwa app! Thanks you all for the amazing logical structures proposed here!

@stephanboeni
Copy link

Why not use the CSS light-dark approach?

color_scheme: light dark;
theme_color:  light-dark(red, darkred);

@bel7aG
Copy link

bel7aG commented Nov 3, 2024

Dynamic CSS variables

currently we are not allowed to serve a dynamic css variable to manifest, otherwise this can fix the current issue and open for better future suggestions/integrations.

"theme_color": "hsl(var(--background))"

@christianliebel
Copy link
Member

@bel7aG Thank you for your suggestion. Unfortunately, we cannot support custom CSS properties (variables) because the manifest is not associated with any CSS. The manifest needs to be independently parsable without any "outside knowledge." After parsing, the (static) values are handed off to the operating system. We will rather have to introduce a new member specifically for these use cases, as laid out in #975 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests