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