Skip to main content
⚡ Calmops

Progressive Web Apps: Web App Manifest and Native App Features

Introduction

Progressive Web Apps (PWAs) bridge the gap between websites and native apps. With a Web App Manifest and Service Worker, your website can be installed on a user’s home screen, work offline, and feel like a native app — without going through an app store.

What Makes a PWA “Installable”?

For a browser to offer the “Add to Home Screen” prompt, your app needs:

  1. HTTPS — required for security
  2. Web App Manifest — describes the app (name, icons, colors)
  3. Service Worker — enables offline functionality
  4. Engagement criteria — user has visited the site multiple times (Chrome’s heuristic)

The Web App Manifest

The manifest is a JSON file that tells the browser how to display your app when installed. Place it at the root of your site (e.g., /manifest.json) and link it in your HTML.

manifest.json

{
  "name": "My Awesome App",
  "short_name": "MyApp",
  "description": "A progressive web app that does amazing things",
  "start_url": "/",
  "display": "standalone",
  "orientation": "portrait",
  "background_color": "#ffffff",
  "theme_color": "#2196F3",
  "lang": "en",
  "scope": "/",
  "icons": [
    {
      "src": "/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable any"
    }
  ],
  "screenshots": [
    {
      "src": "/screenshots/desktop.png",
      "sizes": "1280x720",
      "type": "image/png",
      "form_factor": "wide",
      "label": "Desktop view"
    },
    {
      "src": "/screenshots/mobile.png",
      "sizes": "390x844",
      "type": "image/png",
      "form_factor": "narrow",
      "label": "Mobile view"
    }
  ]
}
<head>
  <link rel="manifest" href="/manifest.json">

  <!-- Theme color for browser chrome -->
  <meta name="theme-color" content="#2196F3">

  <!-- iOS Safari specific (doesn't use manifest) -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="default">
  <meta name="apple-mobile-web-app-title" content="MyApp">
  <link rel="apple-touch-icon" href="/icons/icon-192x192.png">

  <!-- Windows tiles -->
  <meta name="msapplication-TileImage" content="/icons/icon-144x144.png">
  <meta name="msapplication-TileColor" content="#2196F3">
</head>

Display Modes

The display field controls how the app looks when launched from the home screen:

Mode Description Use Case
standalone Looks like a native app — no browser UI Most apps
fullscreen No browser UI, no status bar Games
minimal-ui Minimal browser controls (back, reload) Content sites
browser Regular browser tab Default web behavior

Generating Icons

You need icons in multiple sizes. Tools to generate them:

# Using pwa-asset-generator
npx pwa-asset-generator logo.png ./icons \
  --manifest manifest.json \
  --index index.html

Maskable Icons

Maskable icons fill the entire icon shape on Android (no white padding). Add "purpose": "maskable" to icons that are designed for this:

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

Registering a Service Worker

A Service Worker is required for full PWA functionality (offline support, background sync, push notifications):

// In your main JavaScript file
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const registration = await navigator.serviceWorker.register('/sw.js');
      console.log('Service Worker registered:', registration.scope);
    } catch (error) {
      console.error('Service Worker registration failed:', error);
    }
  });
}
// sw.js — basic caching service worker
const CACHE_NAME = 'myapp-v1';
const ASSETS_TO_CACHE = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/icons/icon-192x192.png'
];

// Install: cache assets
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS_TO_CACHE))
  );
});

// Fetch: serve from cache, fall back to network
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => {
      return cached || fetch(event.request);
    })
  );
});

// Activate: clean up old caches
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(
        keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))
      )
    )
  );
});

Handling the Install Prompt

You can intercept the browser’s install prompt and show it at the right moment:

let deferredPrompt;

window.addEventListener('beforeinstallprompt', (event) => {
  // Prevent the default mini-infobar
  event.preventDefault();
  deferredPrompt = event;

  // Show your custom install button
  document.getElementById('install-btn').style.display = 'block';
});

document.getElementById('install-btn').addEventListener('click', async () => {
  if (!deferredPrompt) return;

  deferredPrompt.prompt();
  const { outcome } = await deferredPrompt.userChoice;
  console.log(`User ${outcome === 'accepted' ? 'accepted' : 'dismissed'} the install prompt`);
  deferredPrompt = null;
  document.getElementById('install-btn').style.display = 'none';
});

// Track successful installation
window.addEventListener('appinstalled', () => {
  console.log('PWA was installed');
  deferredPrompt = null;
});

iOS Safari Considerations

iOS Safari doesn’t support the beforeinstallprompt event or automatic install prompts. Users must manually add to home screen via the Share menu. You need to:

  1. Provide apple-touch-icon meta tags
  2. Show a manual instruction to iOS users
// Detect iOS
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const isInStandaloneMode = window.matchMedia('(display-mode: standalone)').matches;

if (isIOS && !isInStandaloneMode) {
  // Show iOS-specific install instructions
  document.getElementById('ios-install-hint').style.display = 'block';
}
<!-- iOS install hint -->
<div id="ios-install-hint" style="display:none">
  <p>Install this app: tap <img src="/icons/share.svg" alt="Share"> then "Add to Home Screen"</p>
</div>

Testing Your PWA

Chrome DevTools

  1. Open DevTools → Application tab
  2. Check “Manifest” — verify all fields are correct
  3. Check “Service Workers” — verify registration
  4. Run “Lighthouse” audit → PWA category

Lighthouse CLI

npm install -g lighthouse
lighthouse https://yoursite.com --view --preset=desktop

PWA Checklist

  • HTTPS enabled
  • manifest.json linked in HTML
  • Icons in 192x192 and 512x512 (minimum)
  • start_url set correctly
  • display: standalone or fullscreen
  • Service Worker registered
  • Offline page works
  • theme_color and background_color set
  • iOS meta tags added
  • Lighthouse PWA score ≥ 90

Resources

Comments