Progressive Web Apps (PWA)
App Installation (Add to Home Screen)
App Installation and Add to Home Screen
One of the defining features of PWAs is the ability to be installed on a user's device, providing an app-like experience that launches from the home screen. This lesson covers how to implement and customize the installation experience.
Installation Criteria
Before a PWA can be installed, it must meet certain requirements:
Installation Requirements:
- HTTPS: Served over a secure connection (except localhost)
- Web App Manifest: Valid manifest.json file with required fields
- Service Worker: Registered service worker that handles fetch events
- Icons: At least one icon (192x192 or larger)
- User Engagement: User has interacted with the site (Chrome only)
The beforeinstallprompt Event
Capture the install prompt and display it at an appropriate time:
let deferredPrompt;
let installButton;
window.addEventListener('beforeinstallprompt', (event) => {
// Prevent the default install prompt
event.preventDefault();
// Store the event for later use
deferredPrompt = event;
// Show custom install button
installButton = document.getElementById('install-button');
if (installButton) {
installButton.style.display = 'block';
}
console.log('Install prompt available');
});
// Handle install button click
if (installButton) {
installButton.addEventListener('click', async () => {
if (!deferredPrompt) {
console.log('Install prompt not available');
return;
}
// Show the install prompt
deferredPrompt.prompt();
// Wait for user response
const { outcome } = await deferredPrompt.userChoice;
console.log(`User response: ${outcome}`);
if (outcome === 'accepted') {
console.log('User accepted the install prompt');
} else {
console.log('User dismissed the install prompt');
}
// Clear the deferred prompt
deferredPrompt = null;
// Hide install button
installButton.style.display = 'none';
});
}
Best Practices for Install Prompts:
- Wait for user engagement before showing the prompt
- Provide context about why installing is beneficial
- Make the install button visually appealing but not intrusive
- Don't show the prompt immediately on page load
- Respect users who dismiss the prompt
Custom Install UI
Create a custom install prompt that fits your app's design:
<!-- Custom install banner -->
<div id="install-banner" class="install-banner" style="display: none;">
<div class="banner-content">
<img src="/icon-192x192.png" alt="App Icon" class="banner-icon">
<div class="banner-text">
<h3>Install Our App</h3>
<p>Get quick access and work offline</p>
</div>
<div class="banner-actions">
<button id="install-accept" class="btn-primary">Install</button>
<button id="install-dismiss" class="btn-secondary">Not Now</button>
</div>
</div>
</div>
<style>
.install-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
padding: 16px;
z-index: 1000;
}
.banner-content {
display: flex;
align-items: center;
gap: 16px;
max-width: 1200px;
margin: 0 auto;
}
.banner-icon {
width: 64px;
height: 64px;
border-radius: 12px;
}
.banner-text {
flex: 1;
}
.banner-text h3 {
margin: 0 0 4px 0;
font-size: 18px;
}
.banner-text p {
margin: 0;
color: #666;
font-size: 14px;
}
.banner-actions {
display: flex;
gap: 8px;
}
</style>
<script>
let deferredPrompt;
const installBanner = document.getElementById('install-banner');
const installAccept = document.getElementById('install-accept');
const installDismiss = document.getElementById('install-dismiss');
window.addEventListener('beforeinstallprompt', (event) => {
event.preventDefault();
deferredPrompt = event;
// Show banner after 30 seconds of engagement
setTimeout(() => {
if (installBanner) {
installBanner.style.display = 'block';
}
}, 30000);
});
installAccept?.addEventListener('click', async () => {
if (deferredPrompt) {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log(`Install outcome: ${outcome}`);
deferredPrompt = null;
}
installBanner.style.display = 'none';
});
installDismiss?.addEventListener('click', () => {
installBanner.style.display = 'none';
// Remember dismissal for 7 days
localStorage.setItem('install-dismissed', Date.now() + (7 * 24 * 60 * 60 * 1000));
});
</script>
Tracking Installation
Monitor when users install your PWA and how they launch it:
// Track when app is installed
window.addEventListener('appinstalled', (event) => {
console.log('PWA was installed');
// Send analytics event
if (window.gtag) {
gtag('event', 'pwa_install', {
method: 'browser_prompt'
});
}
// Hide any install prompts
const installButton = document.getElementById('install-button');
if (installButton) {
installButton.style.display = 'none';
}
});
// Detect how app was launched
function detectLaunchMode() {
const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
const isInWebAppiOS = (window.navigator.standalone === true);
const isInWebAppChrome = window.matchMedia('(display-mode: standalone)').matches;
if (isStandalone || isInWebAppiOS || isInWebAppChrome) {
console.log('Launched as installed PWA');
// Track standalone launch
if (window.gtag) {
gtag('event', 'pwa_launch', {
display_mode: 'standalone'
});
}
} else {
console.log('Launched in browser');
}
}
detectLaunchMode();
App Shortcuts
Define app shortcuts in your manifest to provide quick access to key features:
{
"name": "My PWA",
"short_name": "PWA",
"start_url": "/",
"display": "standalone",
"shortcuts": [
{
"name": "New Message",
"short_name": "Message",
"description": "Compose a new message",
"url": "/messages/new",
"icons": [
{
"src": "/images/shortcut-message.png",
"sizes": "192x192",
"type": "image/png"
}
]
},
{
"name": "Settings",
"short_name": "Settings",
"description": "Open app settings",
"url": "/settings",
"icons": [
{
"src": "/images/shortcut-settings.png",
"sizes": "192x192",
"type": "image/png"
}
]
}
]
}
Platform Limitations: App shortcuts are currently supported on Android and Windows, but not on iOS. Always test on target platforms.
App Badges
Update the app icon badge to show notification counts or status:
// Set badge count
if ('setAppBadge' in navigator) {
navigator.setAppBadge(5); // Shows "5" on app icon
}
// Clear badge
if ('clearAppBadge' in navigator) {
navigator.clearAppBadge();
}
// Update badge when receiving notifications
self.addEventListener('push', async (event) => {
const data = event.data.json();
// Show notification
await self.registration.showNotification(data.title, {
body: data.body,
icon: '/images/icon-192x192.png'
});
// Update badge count
const unreadCount = await getUnreadCount();
if ('setAppBadge' in navigator) {
navigator.setAppBadge(unreadCount);
}
});
Related Applications
Specify related native apps in your manifest:
{
"name": "My PWA",
"related_applications": [
{
"platform": "play",
"url": "https://play.google.com/store/apps/details?id=com.example.myapp",
"id": "com.example.myapp"
},
{
"platform": "itunes",
"url": "https://apps.apple.com/app/id123456789"
}
],
"prefer_related_applications": false
}
Note: Set
prefer_related_applications to true only if you want browsers to suggest the native app instead of installing the PWA.
Complete Installation Flow
class PWAInstaller {
constructor() {
this.deferredPrompt = null;
this.init();
}
init() {
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
this.deferredPrompt = e;
this.showInstallPromotion();
});
window.addEventListener('appinstalled', () => {
this.handleInstalled();
});
this.detectStandalone();
}
showInstallPromotion() {
// Check if user dismissed recently
const dismissed = localStorage.getItem('install-dismissed');
if (dismissed && Date.now() < parseInt(dismissed)) {
return;
}
// Show install UI
const installUI = document.getElementById('install-banner');
if (installUI) {
installUI.style.display = 'block';
}
}
async install() {
if (!this.deferredPrompt) return;
this.deferredPrompt.prompt();
const { outcome } = await this.deferredPrompt.userChoice;
if (outcome === 'accepted') {
console.log('User accepted install');
} else {
localStorage.setItem('install-dismissed', Date.now() + (7 * 24 * 60 * 60 * 1000));
}
this.deferredPrompt = null;
this.hideInstallPromotion();
}
handleInstalled() {
console.log('App installed');
this.hideInstallPromotion();
// Track installation
if (window.gtag) {
gtag('event', 'pwa_install');
}
}
hideInstallPromotion() {
const installUI = document.getElementById('install-banner');
if (installUI) {
installUI.style.display = 'none';
}
}
detectStandalone() {
const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
if (isStandalone) {
console.log('Running as standalone app');
document.body.classList.add('standalone-mode');
}
}
}
// Initialize
const installer = new PWAInstaller();
Exercise:
- Implement the beforeinstallprompt event handler
- Create a custom install button with promotional text
- Track installation events with analytics
- Add app shortcuts to your manifest for key features
- Implement badge notifications for unread counts
- Test the installation flow on different devices