Install prompt UX: when and how to surface your install button
Published
In one line: Capture beforeinstallprompt, wait for a meaningful user signal, then
call prompt() from a click handler — a cold install modal on first paint is almost always
dismissed and wastes your only chance.
The core event lifecycle
Section titled “The core event lifecycle”let deferredPrompt = null;
// 1. Save the event; suppress the mini-infobar.window.addEventListener('beforeinstallprompt', (e) => { e.preventDefault(); deferredPrompt = e; showInstallAffordance(); // reveal your own button or banner});
// 2. Trigger from a user gesture only.installButton.addEventListener('click', async () => { if (!deferredPrompt) return; deferredPrompt.prompt(); const { outcome } = await deferredPrompt.userChoice; deferredPrompt = null; // event is single-use hideInstallAffordance(); if (outcome === 'accepted') recordInstall();});
// 3. Clean up when the browser completes the install.window.addEventListener('appinstalled', () => { deferredPrompt = null; hideInstallAffordance();});Key rules:
e.preventDefault()is the only way to stop the mini-infobar.prompt()must be called inside a user-activation context (click, touch). Calling it outside a gesture is silently ignored.- The saved event is single-use: once
userChoiceresolves, the same event object cannot callprompt()again. Clear the reference and re-listen if you need to re-prompt.
Platform behavior
Section titled “Platform behavior”Android (Chrome / Chromium)
Section titled “Android (Chrome / Chromium)”beforeinstallprompt fires after the browser’s engagement heuristics are satisfied.
Chrome may also show a mini-infobar at the bottom of the viewport — preventDefault()
blocks it. A custom in-page prompt then lets you choose the moment. After the user
accepts, Chrome installs a WebAPK (not a shortcut) and fires appinstalled.
iOS / Safari
Section titled “iOS / Safari”beforeinstallprompt does not fire on Safari (iOS or macOS). There is no
programmatic install API. The only install path is: user taps the Share icon → “Add to
Home Screen.” Your UX responsibility is to instruct the user with a visual hint
(an arrow pointing at the Share icon or a custom banner) rather than trigger the dialog
yourself. Detect whether the app is already installed with
window.navigator.standalone === true to avoid showing the hint to users who have
already added the app.
Desktop (Chrome / Edge)
Section titled “Desktop (Chrome / Edge)”beforeinstallprompt fires on Chrome and Edge for Windows, macOS, Linux, and ChromeOS.
The install experience opens a dialog window rather than a bottom sheet. The mini-infobar
equivalent is a small install icon in the address bar. A custom prompt() call replaces
that with your own flow. appinstalled fires after the install completes.
Firefox
Section titled “Firefox”Firefox for Android supports installation (Firefox 128+) but does not fire
beforeinstallprompt. There is no JavaScript install API; the user initiates from the
browser menu. Desktop Firefox does not support PWA installation.
When to surface the install UI
Section titled “When to surface the install UI”| Signal | Recommended action |
|---|---|
beforeinstallprompt has fired |
Show a subtle install affordance (button or banner) — never a blocking modal |
| First page load, no engagement | Do nothing yet; a cold prompt has extremely low acceptance |
| User completed a key task (checkout, article read, form saved) | Surface the install button prominently |
| User opened the app multiple times in a week | Inline header chip or persistent footer bar |
Already installed (display-mode: standalone) |
Hide all install UI |
| iOS / Safari | Show a “tap Share → Add to Home Screen” hint after 2–3 sessions |
UX anti-patterns to avoid
Section titled “UX anti-patterns to avoid”- Blocking modals on first load. Chrome’s own research shows cold prompts are almost always dismissed.
- Re-prompting immediately after a dismissal. Browsers may stop firing
beforeinstallpromptafter repeated dismissals; treat each prompt as precious. - Hiding the prompt behind obscure navigation. Make the affordance visible but not intrusive.
- Forgetting iOS users.
beforeinstallpromptabsence does not mean iOS users cannot install; it means you must educate them differently.
Detecting already-installed state
Section titled “Detecting already-installed state”const isStandalone = window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true; // Safari iOS only
if (isStandalone) hideInstallAffordance();Compatibility
Section titled “Compatibility”Browser support for beforeinstallprompt varies. See /compatibility/
for current figures.
Practical checklist
Section titled “Practical checklist”-
beforeinstallpromptis captured withpreventDefault()and stored. - Install button is shown only after the event fires.
-
prompt()is called inside a click handler, not at load time. - Deferred event is cleared after
userChoiceresolves. -
appinstalledlistener hides the install UI and records the conversion. - Standalone detection suppresses install UI for already-installed users.
- iOS fallback instructions are provided (Share → Add to Home Screen).