X Xerobit

PWA Icons — Web App Manifest Icons, Maskable Icons, and Apple Touch Icons

Progressive Web Apps need icons in multiple sizes for home screens, app switchers, and splash screens. Learn how to configure web manifest icons, create maskable icons, and set...

Mian Ali Khalid · · 4 min read
Use the tool
Favicon Generator
Generate favicon package from any image or emoji. Multiple sizes (16, 32, 48, 180, 192, 512), manifest.json, and HTML snippet.
Open Favicon Generator →

PWAs need a manifest.json with correctly sized icons to be installable. Missing sizes, wrong MIME types, or forgetting maskable purpose prevent installation on some platforms.

Generate your base favicon and icons with the Favicon Generator.

Web App Manifest icon configuration

{
  "name": "My App",
  "short_name": "App",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#3b82f6",
  "icons": [
    {
      "src": "/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

The minimum installable set: 192×192 and 512×512. Chrome requires 512×512 to show an install prompt.

Maskable icons

Android 8+ applies adaptive icon shapes (circle, squircle, etc.) to app icons. Without maskable purpose, Android uses the full square image without applying the device’s shape.

{
  "icons": [
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any"
    },
    {
      "src": "/icons/icon-maskable-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable"
    }
  ]
}

Maskable icon safe zone: The center 80% of the image (a circle inscribed in 80% of the total area). Keep all important content within this circle — the outer 10% on each side can be clipped by the device shape.

Total icon: 192×192px
Safe zone:  ~154px circle in center (80% of 192 = 153.6)
Padding:    ~19px on each side

Design your maskable icon with the entire canvas filled (no transparent border). Test at maskable.app.

Apple Touch Icon

iOS Safari ignores the Web App Manifest. Use <link> tags in <head>:

<!-- Apple touch icons (iOS/iPadOS) -->
<link rel="apple-touch-icon" sizes="180x180" href="/icons/apple-touch-icon.png">
<link rel="apple-touch-icon" sizes="152x152" href="/icons/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="120x120" href="/icons/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="76x76" href="/icons/apple-touch-icon-76x76.png">

<!-- Fallback for older iOS: just name it apple-touch-icon.png in root -->
<!-- iOS finds /apple-touch-icon.png and /apple-touch-icon-precomposed.png automatically -->

<!-- Splash screens (optional but recommended) -->
<link rel="apple-touch-startup-image" href="/icons/splash-2048x2732.png"
  media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2)">

The most important Apple touch icon size is 180×180 (for iPhone with 3× retina display).

Generate icons with sharp (Node.js)

import sharp from 'sharp'; // npm install sharp

const ICON_SIZES = [72, 96, 128, 144, 152, 180, 192, 384, 512];

async function generateIcons(inputSvg) {
  for (const size of ICON_SIZES) {
    await sharp(inputSvg)
      .resize(size, size)
      .png()
      .toFile(`public/icons/icon-${size}x${size}.png`);
    console.log(`Generated ${size}×${size}`);
  }

  // Maskable icon: add padding (10% on each side = 20% total padding)
  const maskableSize = 192;
  const iconSize = Math.round(maskableSize * 0.8);  // 154px
  const padding = Math.round((maskableSize - iconSize) / 2);  // 19px
  
  await sharp(inputSvg)
    .resize(iconSize, iconSize)
    .extend({
      top: padding,
      bottom: padding,
      left: padding,
      right: padding,
      background: { r: 59, g: 130, b: 246, alpha: 1 },  // brand color
    })
    .resize(maskableSize, maskableSize)
    .png()
    .toFile(`public/icons/icon-maskable-${maskableSize}x${maskableSize}.png`);
}

generateIcons('src/logo.svg');
// vite.config.js
import { VitePWA } from 'vite-plugin-pwa';

export default {
  plugins: [
    VitePWA({
      registerType: 'autoUpdate',
      manifest: {
        name: 'My App',
        short_name: 'App',
        icons: [
          {
            src: 'icons/icon-192x192.png',
            sizes: '192x192',
            type: 'image/png',
          },
          {
            src: 'icons/icon-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'any maskable',
          },
        ],
      },
    }),
  ],
};

Using "purpose": "any maskable" on the same image works, but dedicated maskable icons (with padding) look better on Android adaptive shapes.

Icon checklist

IconSizePurpose
icon-192x192.png192×192Android install
icon-512x512.png512×512Chrome install prompt, splash screen
icon-maskable-192x192.png192×192Android adaptive icon
apple-touch-icon.png180×180iOS home screen
favicon.ico16, 32, 48 in ICOBrowser tab
favicon.svgScalableModern browsers

Related posts

Related tool

Favicon Generator

Generate favicon package from any image or emoji. Multiple sizes (16, 32, 48, 180, 192, 512), manifest.json, and HTML snippet.

Written by Mian Ali Khalid. Part of the Frontend & Design pillar.