Skip to content

Instantly share code, notes, and snippets.

@adactio
Last active December 15, 2025 16:57
Show Gist options
  • Select an option

  • Save adactio/a445544723363d37b9c31a74a03ef928 to your computer and use it in GitHub Desktop.

Select an option

Save adactio/a445544723363d37b9c31a74a03ef928 to your computer and use it in GitHub Desktop.
Web Install HTML web component
class ButtonInstall extends HTMLElement {
init () {
this.button = this.querySelector('button');
if (window.matchMedia('(display-mode: standalone)').matches) {
this.button.remove();
return;
}
if (!navigator.install) {
this.button.remove();
return;
}
this.button.addEventListener('click', async (ev) => {
await navigator.install();
});
}
connectedCallback () {
if (document.readyState !== 'loading') {
this.init();
return;
}
document.addEventListener('DOMContentLoaded', () => this.init());
}
}
customElements.define('button-install', ButtonInstall);
@adactio
Copy link
Author

adactio commented Nov 29, 2025

Usage:

<install-button> <button>Add the app</button> </install-button>

@Danny-Engelman
Copy link

Danny-Engelman commented Dec 12, 2025

That querySelector will only get a <button> when the Web Component is defined after (light)DOM parsed.

When defined before lightDOM is parsed; ie. in the <head> (to prevent those FOUC issues) querySelector will return null

Because the connectedCallback runs on the opening tag (before lightDOM is parsed),
you need to wait a tick before querying the lightDOM.

I made it a (HTML) Web Component, so it also works when its not a HTML Web Component (user added <button>)

<script>
  customElements.define('button-install', class extends HTMLElement {
    connectedCallback() {
      setTimeout(() => { // wait till lightDOM is parsed
        let canInstall = navigator.install && !window.matchMedia('(display-mode: standalone)').matches;
        canInstall = true; // for testing
        if (canInstall) {
          // use existing button or create new button
          let button = this.querySelector("button") || document.createElement('button');
          // append will 'move' existing button, required to create new button
          this.append(Object.assign(button, {
            textContent: button.textContent || "Install this App!", // not overwrite user button label
            // only attaching one listener, event property handler will do
            onclick: async _ => await navigator.install()
          }));
        } else {
          // this.innerHTML = ""; // clear lightDOM content
          this.remove(); // remove <button-install> entirely
        }
      });
    }
  });
  console.log('installable?', navigator.install, window.matchMedia('(display-mode: standalone)').matches);
</script>

<button-install>
  <button>Install the App!</button>
</button-install>

<button-install></button-install>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment