Re: [w3c/manifest] Add support for defining a theme color for both light & dark modes (prefers color scheme) (#975)

dmurph left a comment (w3c/manifest#975)

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":
```json
{
  "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":
```json
{
    "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>
    3. Purpose <any, maskable, monochrome, unspecified>
    4. 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.


-- 
Reply to this email directly or view it on GitHub:
https://github.com/w3c/manifest/issues/975#issuecomment-3861597966
You are receiving this because you are subscribed to this thread.

Message ID: <w3c/manifest/issues/975/3861597966@github.com>

Received on Friday, 6 February 2026 17:07:17 UTC