demo · v139

Install Button Component

A production-ready install button with a full state machine: not-installable, ready, installing, installed, and launch. Simulate each state to see the correct label, icon, and ARIA attributes update in real time.

Origin trial. To trigger a real install dialog: enable chrome://flags/#web-app-installation-api or join the origin trial. The simulator below works without the flag and shows every state transition.

live install button

Not available in this browser

simulate state

feature detection

checking navigator.install…

PWA install criteria

Run these checks to confirm your app meets the criteria before calling navigator.install().

the code

class InstallButton extends HTMLElement {
  #state = 'not-installable';

  constructor() {
    super();
    this.#btn = this.attachShadow({ mode: 'open' })
                    .appendChild(document.createElement('button'));
    window.addEventListener('beforeinstallprompt', e => {
      e.preventDefault();
      this.#deferredPrompt = e;
      this.#setState('ready');
    });
    window.addEventListener('appinstalled', () => this.#setState('installed'));
  }

  async #handleClick() {
    if (this.#state === 'ready') {
      this.#setState('installing');
      // Option A: beforeinstallprompt deferred prompt
      if (this.#deferredPrompt) {
        const { outcome } = await this.#deferredPrompt.prompt();
        this.#setState(outcome === 'accepted' ? 'installed' : 'ready');
        return;
      }
      // Option B: Web Install API (Chrome 139)
      try {
        const result = await navigator.install();
        this.#setState(result?.mode === 'installed' ? 'installed' : 'ready');
      } catch {
        this.#setState('ready');
      }
    } else if (this.#state === 'installed') {
      window.open(location.href, '_blank');
    }
  }

  #setState(s) {
    this.#state = s;
    const labels = {
      'not-installable': 'Install App',
      'ready':           'Install App',
      'installing':      'Installing…',
      'installed':       'Open App',
    };
    this.#btn.textContent = labels[s];
    this.#btn.setAttribute('aria-label', labels[s]);
    this.#btn.disabled = s === 'not-installable' || s === 'installing';
  }
}

see also