Documentation
Install Button

Install Button

Installation

Automatic

npx pwawtf add install-button

Manual Instructions

Register type definition overrides

Firstly, we are going to need to register the global Event type definition overrides as the @types/react type does not include the prompt method.

Create a <root>/register.ts file:

my-app/
├── ...
├── register.ts 
├── ...
└── tsconfig.json

And add the following code:

// <root>/register.ts
declare global {
  interface Event { 
    prompt: () => Promise<void> 
  } 
}
 
export {};

Set up the InstallPromptEvent Context

Next, we need to set up a React Context to store the event emitted by the "beforeinstallprompt" event on page load.

Create a <root>/src/contexts/InstallPromptEvent.tsx file:

my-app/
├── src/
│   └── contexts/
│       └── InstallPromptEvent.tsx 
├── ...
├── register.ts
├── ...
└── tsconfig.json

And add the following code:

// <root>/contexts/InstallPromptEvent.tsx
import * as React from 'react'
 
// 1. Set up React Context for the global install prompt event. 
const InstallPromptEventContext = React.createContext<Event | null>(null) 
 
export function InstallPromptEventProvider({ children }: { children: React.ReactNode }) {
  // 2. Set up state to store the event. 
  const [installPromptEvent, setInstallPromptEvent] = React.useState<Event | null>( 
    null, 
  ) 
 
  // 3. Listen for any changes to the `"beforeinstallprompt"` event. 
  React.useEffect(() => { 
    window.addEventListener('beforeinstallprompt', setInstallPromptEvent) 
    return () => 
      window.removeEventListener('beforeinstallprompt', setInstallPromptEvent) 
  }, []) 
 
  return (
    // 4. Wrap the children in the context provider. 
    <InstallPromptEventContext.Provider value={installPromptEvent}> 
      {children} 
    </InstallPromptEventContext.Provider> 
  )
}
 
// 5. Export a hook to consume the context. 
export function useInstallPromptEvent() { 
  return React.useContext(InstallPromptEventContext) 
} 

Set up the InstallButton Component

After that, we need to set up a React component that will render a button to prompt the user to install the app.

Create a <root>/components/InstallButton.tsx file:

my-app/
├── src/
│   ├── components/
│   │   └── InstallButton.tsx 
│   └── contexts/
│       └── InstallPromptEvent.tsx
├── ...
├── register.ts
├── ...
└── tsconfig.json

And add the following code:

// <root>/components/InstallButton.tsx
import { useInstallPromptEvent } from './InstallPromptEvent' 
 
export function InstallButton() {
  // 1. Set up some state to show "Installing..." UI feedback. 
  const [installing, setInstalling] = React.useState(false) 
 
  // 2. Extract the install prompt event from context. 
  const event = useInstallPromptEvent() 
 
  // 3. Render the button if the app is ready to be installed 
  if (!event) return null 
  return ( 
    <button 
      disabled={installing} 
      onClick={async () => { 
        try { 
          setInstalling(true) 
          await event.prompt() 
        } finally { 
          setInstalling(false) 
        } 
      }} 
    > 
      {installing ? 'Installing...' : 'Install App'} 
    </button> 
  ) 
}

Wire it together

Finally, we can wire it all together in our <root>/src/App.tsx file.

my-app/
├── src/
│   ├── components/
│   │   └── InstallButton.tsx
│   ├── contexts/
│   │   └── InstallPromptEvent.tsx
|   └── App.tsx 
├── ...
├── register.ts
├── ...
└── tsconfig.json
// IMPORTANT: This component must be rendered at the root of your app.
 
import { InstallPromptEventProvider } from './InstallPromptEvent'
import { InstallButton } from './InstallButton'
 
function App() {
  return (
    <InstallPromptEventProvider> 
      <InstallButton /> 
    </InstallPromptEventProvider> 
  )
}

Tada!

That's it! You should now have a working install button in your app like the one below:

Demo

TODO: stackblitz

Next Steps

Styling

TODO:

  • Create styled implementations with Tailwind (https://web.dev/promote-install/ (opens in a new tab))
    • npx pwawtf add install-button --style top-banner
    • npx pwawtf add install-button --style bottom-banner
    • npx pwawtf add install-button --style button
    • npx pwawtf add install-button --style snackbar

iOS Fallback

Conditional Rendering

TODO:

  • hide button when app is installed
  • show fallback warning message when onbeforeinstall is not supported/doesn't get called