npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

universal-pwa

v0.1.2

Published

A universal, framework-agnostic PWA install prompt library. Created by Olayode Yusuf Alani.

Readme

universal-pwa

A universal, framework-agnostic PWA install prompt library. Drop-in ready for any web application in under 60 seconds.

npm version bundle size license TypeScript


Why universal-pwa?

Every PWA needs an install prompt. But every developer ends up re-implementing the same messy browser API from scratch — fighting beforeinstallprompt, iOS quirks, cooldown logic, and custom UI.

universal-pwa solves this once, for everyone:

| Feature | Other libs | universal-pwa | |---|---|---| | Single <script> CDN drop-in | ❌ | ✅ | | No framework required | Partial | ✅ | | iOS Safari step-by-step guide | ❌ | ✅ | | Shadow DOM — zero style conflicts | ❌ | ✅ | | Dismissal cooldown management | ❌ | ✅ | | Headless mode (bring your own UI) | ❌ | ✅ | | React hook + component | ❌ | ✅ | | Typed event system | ❌ | ✅ | | SSR / Next.js safe | ❌ | ✅ |


Quick Start

Option 1 — CDN (works on any website)

Drop one line into any HTML page. Works with WordPress, Laravel, Django, Rails, plain HTML — everything.

<script src="https://cdn.jsdelivr.net/npm/universal-pwa/dist/index.umd.js"></script>
<script>
  UniversalPWA.init({
    appName: 'My App',
    themeColor: '#6200ee',
  })
</script>

Option 2 — npm

npm install universal-pwa
import { init } from 'universal-pwa'

init({
  appName: 'My App',
  themeColor: '#6200ee',
  position: 'bottom',
})

Option 3 — React

npm install universal-pwa
import { usePWAInstall } from 'universal-pwa/react'

function InstallButton() {
  const { isInstallable, install, isIOS } = usePWAInstall({
    appName: 'My App',
  })

  if (!isInstallable) return null

  return (
    <button onClick={install}>
      {isIOS ? 'How to Install' : 'Install App'}
    </button>
  )
}

Or use the ready-made component:

import { PWAInstallButton } from 'universal-pwa/react'

function App() {
  return <PWAInstallButton appName="My App" themeColor="#6200ee" />
}

Platform Support

| Platform | Strategy | |---|---| | ✅ Chrome / Android | Intercepts beforeinstallprompt, calls .prompt() | | ✅ Chrome Desktop | Same as mobile | | ✅ Edge | Same as Chrome | | ✅ Samsung Internet | Same as Chrome | | ✅ iOS Safari | Shows step-by-step "Add to Home Screen" guide | | ⚠️ Firefox | Marks as unsupported (Firefox lacks PWA install support) | | ✅ Already installed | Detects standalone mode — stays silent automatically |


Configuration

All options are optional.

init({
  // App display name shown in the prompt
  appName?: string               // default: document.title

  // App icon URL shown in the prompt
  iconUrl?: string               // default: gradient placeholder

  // Primary theme/accent color
  themeColor?: string            // default: '#6200ee'

  // Where the prompt appears
  position?: 'bottom'            // default: 'bottom'
            | 'top'
            | 'bottom-left'
            | 'bottom-right'

  // Delay before showing the prompt (ms)
  delay?: number                 // default: 3000

  // Days before re-showing after a dismissal
  dismissCooldownDays?: number   // default: 3

  // Custom description text
  description?: string

  // Custom button labels
  installLabel?: string          // default: 'Install App'
  dismissLabel?: string          // default: 'Not now'

  // Lifecycle callbacks
  onInstall?: () => void
  onDismiss?: () => void
  onInstalled?: () => void
})

Headless Mode (Bring Your Own UI)

Skip the default UI entirely and drive everything with events:

import { createEngine } from 'universal-pwa'

const engine = createEngine({ appName: 'My App', delay: 0 })

// Listen for when the app is ready to install
engine.on('installable', () => {
  myCustomBanner.show()
})

// Trigger install from your own button
myInstallButton.addEventListener('click', async () => {
  const outcome = await engine.install() // 'accepted' | 'dismissed' | 'ios' | 'unsupported'
  console.log('User choice:', outcome)
})

// Dismiss
myDismissButton.addEventListener('click', () => engine.dismiss())

Engine Events

| Event | Payload | Description | |---|---|---| | ready | { status, platform } | Engine initialized | | installable | — | Prompt is ready to show | | installed | — | User accepted & app installed | | dismissed | — | User clicked "Not now" | | error | Error | Something went wrong |

Engine API

engine.status         // 'installable' | 'installed' | 'dismissed' | 'unsupported'
engine.platform       // 'chrome-android' | 'ios-safari' | 'chrome-desktop' | ...
engine.isInstallable  // boolean
engine.isInstalled    // boolean

await engine.install()  // triggers the browser install prompt
engine.dismiss()        // records dismissal with cooldown
engine.reset()          // clears stored state (useful for testing)

React API Reference

usePWAInstall(config?)

const {
  status,         // 'installable' | 'installed' | 'dismissed' | 'unsupported'
  platform,       // detected platform string
  isInstallable,  // boolean — show your install button when true
  isInstalled,    // boolean — app is already installed
  isIOS,          // boolean — true on iOS Safari
  isReady,        // boolean — engine has finished initializing
  install,        // async () => 'accepted' | 'dismissed' | 'ios' | 'unsupported'
  dismiss,        // () => void
  reset,          // () => void — for dev/testing
} = usePWAInstall(config?)

<PWAInstallButton />

<PWAInstallButton
  appName="My App"
  themeColor="#6200ee"
  installLabel="Get the App"
  dismissLabel="No thanks"
  position="bottom"
  hideWhenNotInstallable={true}  // hides the button if not installable (default: true)
  onInstalled={() => console.log('installed!')}
/>

How It Works

┌─────────────────────────────────────────────┐
│            Your Web App (any stack)          │
│                                              │
│   <script src="universal-pwa.umd.js">     │
│   UniversalPWA.init({ appName: 'App' })    │
│                                              │
└──────────────────┬──────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────┐
│              InstallEngine                   │
│                                              │
│  1. detectPlatform()  ──► Chrome / iOS?     │
│  2. isAlreadyInstalled? ──► silent exit     │
│  3. isDismissedRecently? ──► respect sleep  │
│  4. listen for `beforeinstallprompt`        │
│  5. emit('installable') after delay         │
└──────────────────┬──────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────┐
│         UIComponent (Shadow DOM)             │
│                                              │
│  • Hermetically sealed CSS                  │
│  • Cannot conflict with host app styles     │
│  • Smooth slide-up animation                │
│  • iOS step-by-step instructions            │
│  • Keyboard (Escape) support                │
└─────────────────────────────────────────────┘

Requirements

Your web app must have a valid manifest.json for the browser to consider it installable:

{
  "name": "My App",
  "short_name": "App",
  "start_url": "/",
  "display": "standalone",
  "icons": [
    { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }
  ]
}

Contributing

Pull requests are welcome! Please read CONTRIBUTING.md first.

git clone https://github.com/doctwaretech/universal-pwa.git
cd universal-pwa
npm install
npm run dev       # watch mode
npm run build     # production build

License

MIT © Olayode Yusuf Alani (Doctware) - DoctwareTECH