# icons manifest member

> icons lists the images a browser and OS use for an installed PWA (src, sizes, type, purpose); it gates Chromium installability and Android maskable shapes.

`icons` is an array of image resources that represent the installed app on the home screen,
in the task switcher, in the install prompt, and in OS settings. Each entry names a file and,
optionally, the sizes it provides, its MIME type, and the `purpose` it is suitable for. The
browser picks the entry that fits each context; the author does not choose which one is used
where.

Chrome 39, Edge 79, Samsung Internet 4.0, Firefox for Android 152, Safari on iOS 15.4, and
Safari 17 on macOS read the member (BCD `html.manifest.icons`). Firefox 157 on desktop installs
no web apps from the manifest, so the member has no consumer there. Safari on iOS reads
`icons` only when no `<link rel="apple-touch-icon">` is present; the link element wins when
both exist (WebKit blog, Safari 15.4).

## Member

- **Type**: array of image resource objects. `src` (string URL, required) is resolved against
  the manifest URL, not the page URL. `sizes` (string) is a space-separated list of
  `<width>x<height>` tokens or `any` for a scalable format. `type` (string) is the MIME type.
  `purpose` (string) is a space-separated list drawn from `any`, `maskable`, and `monochrome`;
  the default is `any`.
- **Default**: an empty array. A browser with no icon to use falls back to its own letter
  tile or generic icon; Chromium refuses to offer installation.
- **Example value**: `[{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }]`.

Chromium's installability check wants one icon of at least 144 px in PNG, SVG, or WebP, with
`sizes` set and `purpose` either absent or including `any`. When none qualifies, DevTools >
Application > Manifest lists `Manifest does not contain a suitable icon - PNG, SVG or WebP
format of at least 144px is required, the sizes attribute must be set, and the purpose
attribute, if set, must include "any".` under **Installability** (Chromium
`installable_logging.cc`). An entry whose `sizes` string parses to nothing is dropped by the
parser with `found icon with no valid size.`; an icon that downloads but fails to decode
blocks installation with `Downloaded icon was empty or corrupted`.

`purpose: "maskable"` marks an icon drawn edge to edge so the OS can cut it into its own shape
(Android adaptive icons, Windows tiles). The specification defines the safe zone as a circle
centred on the icon with a radius of 40 % of the icon's smaller dimension; everything outside
may be cropped (w3.org, "maskable"). A maskable icon is not a substitute for an `any` icon:
a user agent should use an icon only for its stated purpose, so a manifest with only
`maskable` entries leaves Chromium without an installability icon. The fetch is governed by
the document's `img-src` CSP directive, so a cross-origin icon outside that allow-list is
not requested.

:::observed
Chrome 155 on macOS 26 (English UI), DevTools > Application > Manifest, **Icons** section: an
entry declared `"sizes": "192x192"` whose PNG is 180 px square is listed with the warning
`Actual size (180×180)px of icon does not match specified size (192×192)px`; an entry with
`"purpose": "any maskable"` carries `Declaring an icon with 'purpose' of 'any maskable' is
discouraged. It is likely to look incorrect on some platforms due to too much or too little
padding.`; and the checkbox **Show only the minimum safe area for maskable icons** masks each
maskable preview to the 40 % circle.
:::

## Examples

The manifests below move from the minimum Chromium accepts to a set that also covers Android
masks and monochrome contexts; the last example shows what to check when no runtime API exists.

### A complete icon set for Chromium, Safari, and Android

Two PNG sizes satisfy Chromium's install bar and the Android splash screen, a separate
maskable file covers adaptive icons, and a monochrome SVG serves notification badges and
themed icons. `type` is set on every entry so a browser can skip formats it does not decode
without sniffing the bytes.

```json
{
  "icons": [
    { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
    { "src": "/icons/icon-512-maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" },
    { "src": "/icons/icon-mono.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "monochrome" }
  ]
}
```

On Android 16 the launcher uses the maskable file inside the device's icon shape and the 512
px `any` icon on the splash screen; on iOS 26 Safari uses the largest `any` PNG and applies the
iOS corner mask itself, ignoring the maskable entry.

### A maskable icon that survives the crop

The maskable file is a separate asset, not the `any` icon with padding added. The logo stays
inside the central circle (radius 40 % of the width, so for 512 px a 410 px diameter) and the
background is opaque to the edge, because the cropped region shows through as the OS shape.

```html
<!-- Preview the crop without a device: the clip-path is the spec's safe zone. -->
<img src="/icons/icon-512-maskable.png" width="256" height="256"
     style="clip-path: circle(40% at 50% 50%)" alt="Maskable icon, safe zone only">
```

If the logo touches the clipped edge in this preview it will be cut on a round Android launcher
and on a Windows 11 Start tile; Maskable.app (maskable.app) renders the same asset in every
shape Android ships.

### Checking the shipped set when there is no runtime API

No JavaScript API reports which icon the browser chose. The dependable check is a self-audit
of the manifest the page links, which also works as feature detection: a missing
`<link rel="manifest">` or a page served without `fetch` returns `null` instead of throwing.

```js
async function declaredIcons() {
  const link = document.querySelector('link[rel="manifest"]');
  if (!link || !('fetch' in window)) return null; // Nothing to inspect here.
  try {
    const manifest = await fetch(link.href).then((r) => r.json());
    return Array.isArray(manifest.icons) ? manifest.icons : [];
  } catch {
    return null; // Unreachable or malformed manifest.
  }
}

const icons = await declaredIcons();
if (icons === null) {
  console.info('Manifest not readable; skipping icon audit.');
} else {
  const has = (size) => icons.some((i) => String(i.sizes ?? '').split(' ').includes(size));
  const maskable = icons.some((i) => String(i.purpose ?? '').split(' ').includes('maskable'));
  console.table({ has192: has('192x192'), has512: has('512x512'), maskable });
}
```

A scalable SVG entry with `"sizes": "any"` passes Chromium's 144 px bar on its own, but
Safari on iOS 26 does not use SVG for the home-screen icon, so keep at least one PNG beside it.

## See also

- [Installability criteria: what makes a PWA installable](/reference/installation/installability-criteria/)
- [PWAs on Chrome and Android (WebAPK, TWA)](/reference/platforms/chrome-android/)
- [iOS Add to Home Screen: PWA installation on iOS](/reference/installation/ios-add-to-home-screen/)
- [screenshots manifest member](/reference/manifest/screenshots/)
- [Web Application Manifest: icons member](https://www.w3.org/TR/appmanifest/#icons-member) (w3.org)
- [Adaptive icon support in PWAs with maskable icons](https://web.dev/articles/maskable-icon) (web.dev)
- [New WebKit Features in Safari 15.4](https://webkit.org/blog/12445/new-webkit-features-in-safari-15-4/) (webkit.org)