Skip to content

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

Closed

Description

@jensimmons

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.

Activity

  1. malchata commented on May 4, 2021

    @malchata

    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.

  2. dcrousso commented on May 4, 2021

    @dcrousso
    Member

    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?

  3. malchata commented on May 4, 2021

    @malchata

    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.

  4. mirisuzanne commented on May 4, 2021

    @mirisuzanne

    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)" }
    ]
    
  5. tomayac commented on May 5, 2021

    @tomayac
    Contributor

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

  6. mgiuca commented on May 6, 2021

    @mgiuca
    Collaborator

    +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).

  7. aarongustafson commented on May 7, 2021

    @aarongustafson
    Collaborator

    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?

  8. mirisuzanne commented on May 7, 2021

    @mirisuzanne

    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.

  9. aarongustafson commented on May 7, 2021

    @aarongustafson
    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.

  10. conde2 commented on May 12, 2021

    @conde2
    "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

  11. jensimmons commented on May 20, 2021

    @jensimmons
    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.

  12. aarongustafson commented on May 24, 2021

    @aarongustafson
    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.

  13. marcoscaceres commented on Jun 1, 2021

    @marcoscaceres
    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?

  14. marcoscaceres commented on Jun 1, 2021

    @marcoscaceres
    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"
      }
    }
  15. 116 remaining items

  16. dmurph commented on Feb 5, 2026

    @dmurph
    Collaborator

    Discussion at US/EU manifest spec meeting here.

    @christianliebel raised a bunch of good points about src and backwards compatibility - our conclusions:

    • Because we skip icon parsing if ‘src’ is not present (confirmed chrome, webkit gecko), we can use that to ensure we don’t break compatibility
    • Options that seemed best right now
      1. Fully move to the webextensions format, % needing them / us to specify how the ‘any’ size is chosen (first vs fallback). We also need non-square sizes for screenshots.
        • This is a BIG spec change. We would formalize the new format, and have the legacy format ‘parse’ into the new format
        • Code change definitely, but likely not huge. Not a new OS integration
        • Definitely devrel effort
      2. Support the webextensions format but ‘parse’ it into the old format. This, for devs, is the same as the first option, but for us is less work, as the spec change is smaller, and the code changes are smaller
      3. Otherwise, doing only color_scheme addition has backwards compatibility risk UNLESS we can confirm that Gecko & WebKit will always choose the ‘last’ appropriate icon from the list - this is what Chrome does. So devs who add color scheme icons would need to make sure that it is ‘first’ in the list, and they have equivalent icon later in the list for the legacy impls to choose
      4. I18n might be better if the languages are separate, so we could do the above two only for color_scheme, and keep the icons and icons_localized situation. This is easier for translators, but might be harder for devs to know exactly what icon is chosen.
      5. Other options seem kinda sad, like fully denormalized
        • Icons
        • Icons_dark_color
        • Icons_dark_color_localized
        • Icons_localized
        • Etc 😢

    Interested in the thoughts of others.

  17. dmurph commented on Feb 5, 2026

    @dmurph
    Collaborator

    from meeting with @mkruisselbrink:

    With our flat list, we have to look at all of the things at the same time and it's confusing for developers how this works, wereas the webextensions version allows us to look for a matching entry first, and then choose an appropriate size. So, for example, there could be only one entry for "dark_color_scheme", and then all of that same icon will be in that entry. In the manifest icon list, each icon is it's own icon entry, so each would have to have that scheme.

    Our method works but it's kinda hard to read and the algorithm is weirder. The schema is nicer to understand "first, choose the entry that matches your NON-size constraints. Then, choose an icon size that you want"

    The main question for dev-facing changes here is - do we use a new format, or an old format with a scheme (and maybe lang). I guess localization is a question here too. Do others have thoughts? Favorites? I'll throw out that my favorite is maybe option ii), where we do the new format, and the spec turns into the old format to make it easier to initially do.

    The only extra thing we need to do probably is specify the order in which the filters are supplied. e.g.:

    {
    "lang": "en",
      "icons": [
        // French
        {
          "512": "fr-512.png",
          "192": "fr-192.png",
          "32": "fr-32.png",
          "lang": ["fr"]
          "purpose": ["any"]
        },
    
        // Dark
        {
          "512": "dark-512.png",
          "192": "dark-192.png",
          "32": "dark-32.png",
          "purpose": ["any"]
          "color_schemes": ["dark"]
        },
        // Legacy
    ....
      ]
    }
    
    

    If we are french and also dark mode, we can't get both of those constraints. So which constraint do we drop first? We would jsut want to order them, likely, 1. color_schemes, 2. lang. So we would choose the default dark mode icon, even though it is not french.

  18. marcoscaceres commented on Feb 6, 2026

    @marcoscaceres
    Member

    @carlosjeurissen, I guess one could achieve the same thing if we change the key to a space-separated list of language tags:

    {
      "icons": [{},{},{}],
      "icons_localized": {
        "ru be uk": [{},{},{}]
      }
      "dark_theme_color":
        "icons": [{},{},{}],
        "icons_localized": {
          "ru be uk": [{},{},{}]
        }
    }
    

    Doesn't that pretty much achieve the same thing? And has that nice copy/pasta 🍝 quality that @aphillips mentioned.

  19. dmurph commented on Feb 6, 2026

    @dmurph
    Collaborator

    Reflecting after manifest meeting with @marcoscaceres yesterday:

    • The most important things to do dark mode / light mode here are theme color and background color.
    • Icon localization + dark mode / light mode seems probably rare, and I would actually say that it would require some UX changes in Chrome for us to allow to both a light mode and a dark mode icon (as we would need to present them to the user, both). However, I could easily see this just happening one time when the dark mode / light mode preferences changes, where we present it like an update but with better language
      • e.g. "This app has support for a dark/light mode icon. Please approve the icon change for dark/light mode."
      • <approve / ignore / uninstall / learn more>

    So - I think my proposal is this, which IMO solves this nicely in a way that seems readable and allows us to hopefully unify with WebExtensions. It adds the 'type' and other attributes we were using before, but has the new per-size grouping.

    • It adds WxH parsing for icon size
    • It formalizes the choosing algorithm, and makes a static constraint fallback order (happy to hear feedback on this)

    "icons":

    {
      "32": "32-size-fr.png",
      "64": "64-size-fr.png",
      "type": "image/png",
      "lang": "fr",
      "purpose": "any"
    },
    {
      "32": "32-size-fr-mask.png",
      "64": "64-size-fr-mask.png",
      "type": "image/png",
      "lang": "fr",
      "purpose": "maskable"
    },
    {
      "32": "32-size.png",
      "64": "64-size.png",
      "type": "image/png",
      "purpose": "any"
    },
    {
      "32": "32-size-dark.png",
      "64": "64-size-dark.png",
      "type": "image/png",
      "color-scheme": "dark",
      "purpose": "any"
    },
    {
      "32": "32-size-dark-maskable.png",
      "64": "64-size-dark-maskable.png",
      "type": "image/png",
      "color-scheme": "dark",
      "purpose": "maskable"
    },

    "screenshots":

    {
        "1280x720": "screenshots/home.webp",
        "type": "image/webp",
        "form_factor": "wide",
        "label": "Home screen showing main navigation and featured content"
      },
      {
        "1280x720": "screenshots/dashboard.webp",
        "type": "image/webp",
        "platform": "ios",
        "label": "Dashboard view displaying key metrics"
      }

    Algorithm for a user agent to choose an icon group:

    • User agent then specifies:
      • the following 3 constraints, ordered by importance:
        1. Color Scheme <dark, light>
        2. Purpose <any, maskable, monochrome, unspecified>
        3. Language
    • User agent first removes all icon groups / entries that are fully not supported
      • e.g. on desktop we would remove all 'monochrome' groups, as that is not supported, only on Android. Windows doesn't do masking yet, so it would also remove 'maskable'.
      • same with 'platform' for screenshots.
      • user agents can also probably have a maximum size too - e.g. nothing larger than 2096
      • probably remove entries with no icon paths too (or those just don't parse)
    • found_icon_group = null
    • Loop through all icon entries:
      • If the entry satisfies all constraints, set found_icon_group to the group
    • If found_icon_group == null
      • exit if no constrains left with null
        • (This might happen if user agent doesn't require a basic 'any' icon for installation - usually they seem to choose the favicon for this, so for those user agents, the saved favicon can be the fallback)
      • remove the last constraint from the list, and GOTO the loop again 😛

    This matches our current implementation for choosing icons for a platform given a purpose

    Then, algorithm for user agent to choose an icon from a group:

    • User agent specifies an ideal size, WxH
    • any_size_icon_path = null
    • closest_larger_icon = null
    • closest_smaller_icon = null
    • loop through sizes in the icon group
      • if the size matches the ideal size, return that.
      • if the size is larger, set it to closest_larger_icon (do standard distance stuff if there was one already found)
      • if the size is smaller, set it to closest_smaller_icon (do standard distance stuff if there was one already found)
      • if the size matches any, set any_size_icon_path to the icon path
    • if any_size_icon_path != null, then return any_size_icon_path
    • if closest_larger_icon != null, then return closest_larger_icon
    • if closest_smaller_icon != null, then return closest_smaller_icon
    • return null

    Using the above algorithms, examples of queries for groups with

    • light, maskable, french -> *-size-fr-mask.png - found all!
    • light, maskable, english -> *-size.png (not maskable, preferred light)
    • dark, maskable, french -> *-size-dark.png (not french or maskable, preferred dark)

    The existing constraint fallback is what seems correct to me, where the light/dark is the most jarring / should get right, then any vs maskable for platform support, then language. But I'm happy to have that order changed, or... I guess say the user agent can specify. But I think that it's better to have this be extra predictable for devs. We can even make an easy demo site for people to understand, given a manifest, what is chosen when.

  20. aphillips commented on Feb 6, 2026

    @aphillips
    Contributor

    @dmurph A thing to be careful of is that locales ( == language tags) have fallback. For example, fr-FR => fr => root (default resources). This is another reason why we like language maps: the language tag is visible on the outside, making it easier for people managing translations or variations to spot/organize the translations.

    Anything that's defined as UX should be localizable. Some things, such as icons, might only be localized in a few languages. Other things might be localized nearly all the time. The mechanism for localization should be orthogonal to the "mechanical" aspects of selecting the correct resource (size/orientation/etc.) and there should always be a clear default resource that the developer writes (or is required to write) and that is used when locale-specific values are not provided or not available for a given request.

    @marcoscaceres suggests that:

    we change the key to a space-separated list of language tags

    This could make things complicated, since the list must be parsed into tokens which are then prefix compared to the requested locale. In practice, localizers are more likely to want separate entries anyway. The rest of your example is good, from the point of view that there is a clear "source" (icons) for all of the icons_localized that acts as the root locale entry. The localized icons only override the source when provided.

    {
      "icons": [{},{},{}],
      "icons_localized": {
        "fr": [{},{},{}],    // selected for fr, fr-FR, fr-BE, fr-*
        "fr-CA": [{},{},{}]  // selected for fr-CA
      }
    }
    
  21. dmurph commented on Feb 6, 2026

    @dmurph
    Collaborator

    @aphillips I'm assuming that is solved by simply allowing the language constraint matching to do that fallback fr-FR => fr when it's looking for a language constraint match. If that isn't the case, let me know, or what would need to change.

    And yes, a con of this approach is that, say, the french translations are in multiple entries. But they can still be in the same area in the list (but the dev would have to organize that)

    If that is an unacceptable con, the we can keep the existing *_localized.

    And then to clarify what you said for Marcos's suggestion - it's just that it doesn't force the separation, so it might be confusing? It would still seem to work though, right? "fr-BE fr-CA" could be a dict key, and then a separate "fr" dict key could be there - it's just a shortcut to make stuff shorter if people want to.

  22. carlosjeurissen commented on Feb 6, 2026

    @carlosjeurissen
    Member

    icons_localized["ru be uk"]

    @marcoscaceres @aphillips using space separated list of language tags like you are proposing looks quite elegant and makes the yandex case more compelling. If duplicate languages are present as a result of this syntax (ie "ru be" and "ru"), implementations can favour the shortest property ("ru"). Unless there is other subtags (ie "zh-Hans zh-Hant" would win over "zh" if the preferred script is Hans or Hant. It would only make parsing a bit more complicated. Is this "shorthand" something we would want to add to the text version of _localized as well?

    As for icons specifically, it could make the hybrid (option iv. in @dmurph #975 (comment)) less verbose in the yandex case.

    Unlike for normal localized text, localized resources still need the resource files to be put in the right place. This often still involves developers. So this aspect may not be as important as is the case for localized text members.

    @aphillips mentioned:

    The rest of your example is good, from the point of view that there is a clear "source" (icons) for all of the icons_localized that acts as the root locale entry. The localized icons only override the source when provided.

    Just to clarify, given the following syntax:

    {
      "icons": [{a},{b},{c}],
      "icons_localized": {
        "fr-CA": [{d}]
      }
    }

    If the locale is "fr", it would result in [{a},{b},{c}] ?
    (ignoring the fr-CA override as that is more specific than fr?)

    and if the locale is "fr-CA", it would result in [{d}] ?
    (completely discarding any icons in the main icons member, as there is a localized override?)

    Current icons order for equal options

    @dmurph you mentioned Chromium always picks the last one. While it seems to generally mostly uses the last one. I found one case where it chooses the first one. This happens in the "install app" submenu. See screenshot:

    Click to expand screenshot Image

    Screenshots

    @dmurph reading in the meeting notes a reference to screenshots. While the webExtensions syntax seems very useful for icons, as for icons, implementations always have to choose ONE icon. We may not want to adopt this syntax for screenshots. Unlike icons, the screenshots member has already meaning to the order. The first item in the list is the first one you want to be displayed. Merging this with the constraints order seems to result in an order mismatch.

    While using a different syntax for screenshots vs icons sounds counter intuitive. They are already different beasts. Screenshots have a specific order, with properties like form_factor which icons do not have. Screenshots on the other hand do not have the purpose property.

    Many publishers include both light and dark mode screenshots to inform user candidates the app supports both light and dark mode, and how each of them looks like. For screenshots, the color_scheme is always a single value. It could be seen as annotation similar to the current form_factor screenshots property. It is then up to the store or installation prompt to decide whether or not to display both light/dark screenshots.

    Unlike icons, for screenshots, having browsers ignore the color_scheme property seems to not have any significant impact.

    In a similar fashion, the languages for screenshots are also always a single value. When it comes to screenshots, we can either introduce screenshots_localized similar to the icons_localized, or add annotations to each screenshot in the form of a lang: "en" property. Appending each localized version after the default screenshots would allow implementations to filter-out specific languages while older implementations would simply append them to the list of screenshots. (And hopefully do not download and render all screenshots in this list).

    Having all screenshots together without using a separate color_scheme_dark.screenshots bucket would allow authors to express screenshot order with mixed light/dark modes in case stores do not filter either of them. (which is 100% now).

    Constraints precedence

    @dmurph mentioned:

    "If we are french and also dark mode, we can't get both of those constraints. So which constraint do we drop first? We would jsut want to order them, likely, 1. color_schemes, 2. lang. So we would choose the default dark mode icon, even though it is not french."
    There are several approaches here. We can leave the order up to the implementations (messy/unreliable), spec out the exact order (like you mention) OR, we stick to the webExtensions logic. In which the order of the groups determines which constraints take precedence. This basically outsources the precedence responsibility away from the spec/implementation to the manifest author. The latter actually requires the icon-sizes to be grouped like this.

    If one of the icon groups contains properties (ie potential constraints) the implementation does not support / understand. The whole icon group should be skipped and the implementation should look at the next object. This allows future extensions with additional constraints without backwards compatibility risks. For example, a developer may prefer svg over webp over png, and specifies them in the type. If a browser does not understand the type, it goes to the next block. Say a new constraint like high contrast were to be introduced, it will safely be ignored by old implementations without any side effects.

    If we however let the browser determine the precedence, as specified by @dmurph #975 (comment). We can use the same logic for screenshots. For screenshots specifically, we could even re-use the src/sizes as I don't think showing all screenshots is a bad thing see above. On the other hand, adding multiple sizes in the same group could indicate it is the same view in different screen-sizes.

    Just keep in mind currently webExtensions are designed to make the author determine the order. However given icon_variants is not widespread yet, the consequences of changing this behaviour seems low. Especially since webkit currently does not discard icon groups with unknown properties (see w3c/webextensions#229 (comment)).

    @dmurph the reasoning of dropping (unsupported) constraints when no match could be found sounds very compelling! We would need to figure out what properties to discard first.

  23. dmurph commented on Feb 6, 2026

    @dmurph
    Collaborator

    Regarding screenshots:

    • Yes, it's definitely different with wanting multiple and the order mattering more. I believe for screenshots the parts of the 'algorithm' I had above would mostly be executing the pre-filter part, to remove all of the wrong platform stuff, and then just return them all after that. So - same 'algorithm' but the user agent would never specify a constraint list kinda (except for lang) - as screenshots don't have purpose or color scheme.

    Regarding constraints:

    • I agree that there is an initial filter step, then a constraint search step. Definitely the algorithm could return a list if we want more than one, instead of just one. That could be a different mode. That helps for screenshots.
    • For 'constraints not known'.... I 'think' that is OK? I guess for supporting backwards compatibility, if a new constraint is added... yeah you would need to have entries without that new constraint anyways.... so that seems fine....
    • And yes - to me it seems like this can be a statically defined list, and IMO in the real world:
      • if there is a problem with this precidence (e.g. the dark-maskable-french example above where it used the non-french icon due to no french icon specified that was dark), the developer can simply just add the french-maskable-dark icon and fix it.

    I am interested in the opinion of @christianliebel for incorporating the 'lang' into the icons vs separate dicts per lang, like we currently have. e.g. is that better dev ergonomics, especially if we change it so that we can specify multiple lang keys in the dict key ("en": {}, "en-UK en-CA": {}, ...) to help with duplication.

  24. carlosjeurissen commented on Feb 6, 2026

    @carlosjeurissen
    Member

    @dmurph mentioned:

    For 'constraints not known'.... I 'think' that is OK? I guess for supporting backwards compatibility, if a new constraint is added... yeah you would need to have entries without that new constraint anyways.... so that seems fine....

    Could you clarify what you mean with that is OK? Do you agree the icon group should be ignored in the initial search? (Unless the final result is null/0). Indeed for backwards compatibility, you would have to add entries without this constraint/property. However we should specify what should happen when unknown/unsupported properties/values are encountered.

    if there is a problem with this precidence (e.g. the dark-maskable-french example above where it used the non-french icon due to no french icon specified that was dark), the developer can simply just add the french-maskable-dark icon and fix it.

    In that case we are back to the duplication problem. Yet only if the intended precedence order of the author is different than what we may specify. The precedence author-defined order would solve this and defer the responsibility to the author. (saving us the need to spec this out).
    A hybrid could be to use the order in screenshots to determine the gallery order and use the order in icons to determine the precedence order (given you can only select one icon). This may not be as much of a conflict as we may think.

    One more note on dark icons: for webExtensions, icons are mostly transparent. Dark icons are also used in light mode when a browser theme is used with a dark background (like olive green for example). It uses it as clue as what background works well. However seems with the introduction of the liquid glass dark app icons. "dark mode icons" means more than icons working well on a dark background. Seems for maskable icons, sticking with the system dark mode makes most sense. While for the transparent icons, looking at the background seems to make more sense. Yet this is probably up for the implementation to determine.

  25. carlosjeurissen commented on Feb 7, 2026

    @carlosjeurissen
    Member

    Author defined-precedence

    Just to clarify how the author-defined precedence order algorithm would work:

    1. look for the first icon group
    2. for each condition in the object:
      2.1) check if the condition category name (like color_schemes) is understood, else MUST move to the next object
      2.2) if the condition is an empty array. move to the next object.
      2.3) check if the requested condition is matching one of the values specified in the condition, if not, move to the next object. Unknown condition values are ignored as long as one is matched, allowing future extensions.
    3. once an icon group has all matching conditions (or the group specifies no conditions). Take all icon size declarations and use the one which is closest to the requested icon size similar to the existing algorithm we have.

    This could be further refined by progressively removing constraints when reaching the end of the list.

    Spec/implementation defined-precedence

    @dmurph If we conclude deferring the order precedence to the author by them ordering it properly in the icons object is giving them too much responsibility. We may be able to resolve the approach by allowing developers to specify the "any" value for all the constraints.

    With this approach, implementations would follow something similar to the algorithm @dmurph described. With a spec-defined order of precedence. Yet by adding "any" as value for some of the constraints, developers can opt-in to the author-defined order (the current web-extension icon_variants logic). In a way working a bit similar to css selector specificity. ie, using ".class.class" vs ".class". This basically assigns a meaning towards being very explicit and puts the icon groups in the same importance. If all icon groups have the same amount of constraints, (and using the any matches at times), authors can basically opt-in to the author defined order. This replaces duplication with "any" annotations.

    In your example.

    [{
      "512": "fr-512.png",
      "192": "fr-192.png",
      "32": "fr-32.png",
      "lang": ["fr"]
    }, {
      "512": "dark-512.png",
      "192": "dark-192.png",
      "32": "dark-32.png",
      "color_schemes": ["dark"]
    }]

    With your algorithm, color_schemes takes precedence over "lang". Developers could opt-in to author-defined order by changing it to:

    [{
      "512": "fr-512.png",
      "192": "fr-192.png",
      "32": "fr-32.png",
      "lang": ["fr"],
      "color_schemes": ["any"]
    }, {
      "512": "dark-512.png",
      "192": "dark-192.png",
      "32": "dark-32.png",
      "lang": ["any"],
      "color_schemes": ["dark"]
    }]

    This way there is no need for duplication while being more forgiving to inconsiderate / careless authors/developers and generally be more predictable. It seems it could be the best of both worlds, and allowing alignment with screenshots handling. While unpredictable, this would potentially allow implementations to choose their own precedence at times when they find it important. Still this feels more like a patch than anything else. Going for author-defined precedence order could save us a lot of complications for spec and implementations.

  26. dmurph commented on Feb 9, 2026

    @dmurph
    Collaborator

    Ah.... author defined precedence - Sorry I missed that part. That's definitely another option. The main difficulty there, I believe, is merging the old and new icon types in one list, as the old list's behavior is the opposite for Chromium - we use the 'last' appropriate one we find, not the first. I don't think it's impossible to change this behavior - we would want to likely inspect the all of the manifests of the top X installed PWA sites to make sure that this wouldn't cause problems - but yeah. Another change.

    There are sooooo many ideas here, I worry about this conversation never ending.

    What can we agree on that will have impact? what about just the theme & background colors?

    @marcoscaceres had this suggestion above I think - What do we all think about this? This seems fine to me:

      "color_scheme_dark": {
        "theme_color": "#5f7995",
        "background_color": "#ff7ff5"
      }

    Maybe we can agree on this part, and move the icon discussion to another thread?

  27. christianliebel commented on Feb 10, 2026

    @christianliebel
    Member

    I would agree to the color part. Here’s a PR that only introduces dark theme support for colors: #1207

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions