HTML in Progressive Web Apps (PWAs)
Mentor's Note: A Progressive Web App is a website that behaves like a native app. It can be installed on your phone's home screen, work offline, send push notifications, and even access device hardware — all built with standard HTML, CSS, and JavaScript. The key word is progressive — it starts as a regular website and progressively gains superpowers.
What is a PWA?
A Progressive Web App is a web application that uses modern browser capabilities to deliver an app-like experience. PWAs are built with standard web technologies (HTML, CSS, JavaScript) but gain native-app features like offline support, home screen installation, and push notifications.
Core Requirements
A web app must meet three criteria to be considered a PWA:
- HTTPS — secure connection is mandatory
- Web App Manifest — a JSON file that defines how the app appears when installed
- Service Worker — a JavaScript file that runs in the background, handling caching and offline support
App Manifest (manifest.json)
The Web App Manifest is a JSON file that tells the browser how your app should behave when installed. You link it in HTML with <link rel="manifest">.
HTML Link
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My PWA App</title>
<link rel="manifest" href="/manifest.json">
<!-- iOS fallback for older Safari versions -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="apple-touch-icon" href="/icons/icon-192.png">
</head>
manifest.json
{
"name": "My PWA App",
"short_name": "PWA App",
"description": "A progressive web application built with HTML",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1a1a2e",
"orientation": "portrait-primary",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"categories": ["education", "utilities"],
"lang": "en-US",
"dir": "ltr"
}
Manifest Properties Summary
| Property | Purpose | Example |
|---|---|---|
name | Full app name shown on splash screen | "My PWA App" |
short_name | Short name for home screen (≤12 chars) | "PWA App" |
description | Description for app stores / install prompts | "A PWA for learning" |
start_url | Entry point URL when launched | "/" or "/?source=pwa" |
display | How the app is displayed | "standalone", "fullscreen" |
background_color | Splash screen background | "#ffffff" |
theme_color | Browser chrome/toolbar colour | "#1a1a2e" |
orientation | Locked orientation | "portrait-primary" |
icons | Array of app icons at various sizes | See above |
scope | URLs the app controls | "/" |
categories | App store categories | ["education"] |
Display Modes
The display property controls how the PWA looks when launched from the home screen:
| Mode | Description | Best For |
|---|---|---|
fullscreen | Hides all browser UI — immersive full screen | Games, video players, presentations |
standalone | Opens in a separate window without address bar | Most apps — the standard PWA mode |
minimal-ui | Shows minimal browser navigation (back, refresh) | Utility and reference apps |
browser | Opens in a regular browser tab (default) | Fallback — no app-like experience |
Service Worker Registration in HTML
The service worker is registered via JavaScript in your HTML page. It acts as a network proxy — intercepting requests and serving cached responses when offline.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Offline-Ready PWA</title>
<link rel="manifest" href="/manifest.json">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>My Offline PWA</h1>
<p>This app works offline after the first visit.</p>
<script>
// Register the service worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW registered:', registration.scope);
})
.catch(error => {
console.error('SW registration failed:', error);
});
});
}
</script>
</body>
</html>
A Minimal Service Worker (sw.js)
const CACHE_NAME = 'my-pwa-cache-v1';
const ASSETS_TO_CACHE = [
'/',
'/styles.css',
'/app.js',
'/icons/icon-192.png',
'/icons/icon-512.png'
];
// Install — cache the app shell
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(ASSETS_TO_CACHE))
.then(() => self.skipWaiting())
);
});
// Activate — clean old caches
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(name => name !== CACHE_NAME)
.map(name => caches.delete(name))
);
})
);
});
// Fetch — serve from cache, fall back to network
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
.catch(() => {
// Offline fallback page
return caches.match('/offline.html');
})
);
});
The service worker runs in its own global scope (ServiceWorkerGlobalScope), not the page's window. It has no DOM access and can't directly interact with the page.
WebView — HTML Inside Native Apps
A WebView is a browser engine embedded inside a native mobile app. When you see HTML content inside an Android or iOS app (like a login screen, terms of service, or embedded article), it's likely running in a WebView.
WebView vs PWA
| Aspect | WebView (in native app) | PWA (installed from browser) |
|---|---|---|
| Distribution | App Store / Google Play | Browser (no store required) |
| Updates | Requires app store update | Instant — server-side update |
| Capabilities | Full native API access (bridged) | Limited to web APIs |
| Installation | User downloads from app store | User adds to home screen |
| Size | Native + WebView overhead | Small (HTML + cache) |
| Offline | Depends on implementation | Built-in via service worker |
Splash Screens and Icons
When a PWA launches, the browser shows a splash screen based on the manifest properties while the app loads.
How Splash Screens Work
The browser generates the splash screen automatically from:
background_color— fills the backgroundicons— the largest icon in the manifest is centred on the screen (ideally 512×512)
{
"name": "My PWA App",
"background_color": "#1a1a2e",
"icons": [
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
Icon Requirements
| Size | Purpose |
|---|---|
| 192×192 | Home screen icon, install prompt |
| 512×512 | Splash screen, high-res displays |
| 48×48 | Notification icon |
| 96×96 | Favicon alternative |
The "purpose": "maskable" flag tells the browser the icon has safe padding so it can be cropped into different shapes (circle, squircle, rounded square) depending on the platform.
Offline Support Concept
PWAs use the Cache API (available in the service worker) to store responses and serve them when the network is unavailable.
Caching Strategies
| Strategy | Best For | Behaviour |
|---|---|---|
| Cache First | Static assets (CSS, JS, icons) | Serve from cache, update from network |
| Network First | Dynamic content (API data, articles) | Try network first, fall back to cache |
| Stale-While-Revalidate | Mixed content | Serve cached instantly, fetch update in background |
| Network Only | Real-time data (chat, stocks) | Always fetch fresh from network |
| Cache Only | Offline-first apps | Never hits the network |
Try It Yourself — PWA Checklist
Build Your First PWA
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My First PWA</title>
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#1a1a2e">
<link rel="apple-touch-icon" href="/icons/icon-192.png">
<style>
body {
font-family: system-ui, sans-serif;
max-width: 600px;
margin: 2rem auto;
padding: 0 1rem;
text-align: center;
}
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 2rem;
margin: 2rem 0;
background: #f9f9f9;
}
button {
padding: 12px 24px;
font-size: 1rem;
background: #1a1a2e;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.status {
margin-top: 1rem;
padding: 0.5rem;
border-radius: 4px;
font-size: 0.9rem;
}
.online { background: #d4edda; color: #155724; }
.offline { background: #f8d7da; color: #721c24; }
</style>
</head>
<body>
<h1>My First PWA</h1>
<div class="card">
<p>This page should work offline after your first visit.</p>
<p id="connection-status" class="status online">✅ You are online</p>
<button onclick="fetchData()">Test Network</button>
<pre id="output"></pre>
</div>
<script>
// Register service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
// Track online/offline status
window.addEventListener('online', () => {
document.getElementById('connection-status').textContent = '✅ You are online';
document.getElementById('connection-status').className = 'status online';
});
window.addEventListener('offline', () => {
document.getElementById('connection-status').textContent = '❌ You are offline';
document.getElementById('connection-status').className = 'status offline';
});
function fetchData() {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(r => r.json())
.then(data => {
document.getElementById('output').textContent = JSON.stringify(data, null, 2);
})
.catch(err => {
document.getElementById('output').textContent = 'Network error: ' + err.message;
});
}
</script>
</body>
</html>
PWA Readiness Checklist
- HTTPS — site served over HTTPS (required for service workers)
- manifest.json — valid manifest with name, icons, display, theme_color
- Service worker — registered and handles fetch events
- Offline support — app shell is cached and served when offline
- Responsive design — works on mobile, tablet, desktop
- Icons — 192×192 and 512×512 icons with maskable purpose
- Theme color — matches your app's brand
- Fast load — performs well on slow networks
- Install prompt — browser shows add-to-home-screen dialog
- Tested on device — opened on actual Android/iOS hardware
Pro tip: Use Lighthouse in Chrome DevTools (Audits tab) to run a PWA audit. It checks all the requirements and gives you a score with actionable feedback.
PWA vs Native Apps
| Feature | PWA | Native App |
|---|---|---|
| Installation | Via browser (zero friction) | App store download |
| Updates | Instant (server push) | App store review process |
| Storage | Browser storage (~50MB+) | Full device storage |
| Push notifications | Supported | Supported |
| Offline | Via service worker caching | Full offline support |
| Device APIs | Limited to web APIs | Full native API access |
| SEO | Indexable by search engines | Not indexable |
| Cost | Single codebase (web) | iOS + Android codebases |
🔗 Related Topics
- HTML5 Semantic Tags — The semantic foundation for PWAs
- HTML5 APIs — Browser APIs that power PWA features
- DevTools — Debug and audit your PWA