Install Prompt React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React PWA install prompt: the browser's install dialog in Chrome and Edge, and an Add to Home Screen guide on iPhone and Mac.
A few seconds after the page can be installed, a slim card with the app's icon, name and an Install button slides up from the bottom (or drops from the top) and stays out of the way: it never takes focus and the page keeps working under it. On Android and desktop Chrome or Edge, Install opens the browser's own install dialog; the button shows a spinner while it is open, and once the app is installed the card says Installed with a check for two seconds and leaves. On an iPhone or iPad, where browsers have no such dialog, Install opens a bottom sheet with three numbered steps (tap Share, choose Add to Home Screen, tap Add), each with the icon you will look for, and on an iPhone an arrow at the bottom of the sheet bobs twice toward Safari's share button. On a Mac with Safari the card shows the Add to Dock steps inline. The × (named Not now) hides it for dismissDays and remembers that in localStorage; closing the sheet by dragging it down, tapping outside or pressing Escape counts the same. On a desktop Chrome window the banner is a 380px card in the bottom end corner. With the keyboard, Tab reaches Install and ×, Enter or Space presses them, and in the sheet (a centered dialog from 768px up) Tab stays inside and Escape closes it, returning focus to the button that opened it. Installed or already running as an app, it renders nothing. The outcome is announced to screen readers. Under reduced motion it fades instead of sliding.
Install Prompt is a Pro React component in the React mobile components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add install-prompt, together with UIProvider, Button, Drawer, useControllableState, useMediaQuery and 3 more and the npm packages motion and lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add install-promptInstall Prompt is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import InstallPrompt from @/components/ui/install-prompt.
// app/layout.tsx: mount it once in the root layout, the browser offers the install only once and early
import type { ReactNode } from "react"
import { InstallPrompt } from "@/components/ui/install-prompt"
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
{children}
<InstallPrompt
appName="Northwind CRM"
appIcon="/icon-192.png"
description="Open it right from your home screen, even offline."
labels={{
notNow: "Maybe later",
iosSteps: ["Tap Share in the bottom bar", "Choose Add to Home Screen", "Tap Add"],
}}
/>
</body>
</html>
)
}Props
appNamedefault""stringThe app's name, used in the titles ("Install Northwind CRM") and for the letter tile. Empty falls back to labels.fallbackName ("this app").
appIcondefault""string | ReactNodeThe icon: an image src (shown 48px with 12px corners, use your 192px manifest icon) or an element. Empty draws a tile in the tone with the first letter of appName.
descriptiondefault""stringOne line under the title: why installing helps. Truncated in the slim banner, wrapped in the sheet and the card.
screenshotsstring[]Sheet variant: a horizontal rail of screenshots (h-72, 9:16, snapping) between the title and the buttons, keyboard scrollable.
variantdefault"banner""banner" | "sheet" | "card"banner: a slim floating card pinned to placement, like a Smart App Banner (on iPhone its Install opens the guide sheet; on a Mac it shows the steps inline). sheet: a bottom sheet with details, a centered dialog from 768px up. card: an inline card you place yourself (a settings page), always visible while the app can be installed.
placementdefault"bottom""bottom" | "top"The banner's edge. Bottom sits above a bottom tab bar and a sticky CTA bar (--bottom-bar-offset, --cta-bar-offset) and the home indicator; top sits under the notch, or under --install-prompt-top when you set it (a fixed header).
positiondefault"fixed""fixed" | "absolute"fixed pins the banner to the viewport (and turns it into a 380px card at the bottom end on desktop); absolute pins it inside the nearest positioned parent (a phone frame, a panel).
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"The Install button and the letter tile. Keep neutral in an app; brand fits a marketing page.
colordefault""stringAny CSS color for the Install button and the letter tile, replacing the tone.
radiusdefault"md""sm" | "md" | "lg"Corners of the card and the floating banner: 12, 16 or 24px.
platformdefault"auto""auto" | "chromium" | "ios" | "macos-safari" | "unsupported"auto detects it: chromium once the browser has offered an install (beforeinstallprompt), ios on iPhone and iPad in any browser, macos-safari on Safari 17+ for Mac; in-app browsers and everything else are unsupported and render nothing. Force one for demos and QA; a forced chromium without a real event makes Install resolve "unavailable".
delaydefault3000numberMilliseconds after it becomes possible before the banner or sheet shows, so it never greets people on arrival. Ignored while open is controlled and by the card.
dismissDaysdefault14numberHow long Not now hides it, in days. 0 hides it for this session only (sessionStorage).
storageKeydefault"install-prompt"stringThe localStorage key that remembers Not now (a timestamp). Empty keeps it in memory; with storage blocked it lasts the session.
showOnIOSdefaulttruebooleanShow the Add to Home Screen guide on iPhone and iPad.
showOnDesktopdefaulttruebooleanShow it in Chrome and Edge on a computer and the Add to Dock guide in Safari for Mac.
defaultOpendefaultfalsebooleanOpen the banner or sheet at once (no delay, a previous Not now ignored). An initially open banner skips its entrance.
openbooleanControlled open state of the banner or sheet; pair it with onOpenChange. It still renders nothing where there is no install path.
onOpenChange(open: boolean) => voidCalled when it opens after the delay and when it closes (Not now, installed, the sheet dismissed).
onInstall(outcome: "accepted" | "dismissed" | "unavailable") => voidThe result of Install in Chromium: accepted, dismissed in the browser dialog (the prompt cannot be shown again until the browser offers it again), or unavailable (no pending event).
onDismiss() => voidNot now, the ×, Got it in the guide, or the sheet closed by drag, tap outside or Escape.
safeAreadefaulttruebooleanPad the banner off the notch, the home indicator and the rounded side edges (env(safe-area-inset-*)).
desktopdefault"dialog""dialog" | "sheet"What the sheet (and the iPhone guide) becomes at 768px and up: a centered dialog, or the same bottom sheet capped at 420px.
containerHTMLElement | nullPortal target of the sheet (default document.body). A transformed or contain:layout element keeps the sheet inside it, like a phone frame.
labelsPartial<InstallPromptLabels>Every string: install "Install", notNow "Not now", title(name) "Install {name}", iosTitle(name) "Add {name} to your Home Screen", iosSteps ["Tap Share in the toolbar", "Scroll and choose Add to Home Screen", "Tap Add"], macTitle(name) "Add {name} to the Dock", macSteps ["Choose File in the menu bar", "Choose Add to Dock"], installed "Installed", fallbackName "this app", close "Close", done "Got it", installedAnnouncement(name) "{name} was installed", screenshots "Screenshots", screenshot(i, n) "Screenshot {i} of {n}", addGlyph "Add", fileGlyph "File". Defaults in DEFAULT_INSTALL_PROMPT_LABELS.
classNamesPartial<Record<"icon" | "content" | "title" | "description" | "screenshots" | "steps" | "step" | "actions" | "install" | "dismiss", string>>Extra classes for the inner parts.
classNamestringExtra classes for the root: the banner's pinned wrapper, the card, or the sheet's content.
Parts
useInstallPrompt
hookThe same engine without the UI, for your own button (a profile menu item). One store per page listens for beforeinstallprompt and appinstalled from the first call on, so call it (or mount InstallPrompt) in the root layout: the event fires once, early. canInstall is true while the browser prompt is pending (chromium) or the guide applies (ios, macos-safari). Signature: (options?: { platform?: "auto" | InstallPlatform; storageKey?: string; dismissDays?: number }) => { platform: InstallPlatform; canInstall: boolean; isStandalone: boolean; installed: boolean; dismissed: boolean; promptInstall: () => Promise<"accepted" | "dismissed" | "unavailable">; dismiss: () => void; reset: () => void }.
DEFAULT_INSTALL_PROMPT_LABELS
constantThe English defaults, to spread and override. Type: InstallPromptLabels.
Examples
iPhone guide
The only path on iOS: three steps with the icons to look for, and an arrow toward Safari's share button.
Install sheet with screenshots
Chromium: the sheet shows what you get, then Install opens the browser's own dialog.
Card in settings
variant="card" sits in the page and stays while the app can be installed.
Top banner, brand tone
placement="top" under the status bar, for a marketplace's landing screen.
Safari on a Mac
The Add to Dock guide as a card: File, then Add to Dock.
Your own button
useInstallPrompt drives a menu item; the flags are this browser's real state.
Dependencies
Files
- components/ui/install-prompt.tsx
FAQ
Is the React Install Prompt component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Install Prompt?
Run npx wingo-ui@latest add install-prompt. It also adds the Wingo UI items it builds on (UIProvider, Button, Drawer, useControllableState, useMediaQuery and 3 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/install-prompt.
How does Install Prompt behave on phones?
On an iPhone or iPad, where browsers have no such dialog, Install opens a bottom sheet with three numbered steps (tap Share, choose Add to Home Screen, tap Add), each with the icon you will look for, and on an iPhone an arrow at the bottom of the sheet bobs twice toward Safari's share button.
See also
More React mobile components
Articles using this component
- Nextjs PWA Without next-pwa: Offline Page and App Shell
Mobile UI in React12 min read
- React Native vs PWA in 2026: Which One Do You Need?
Comparisons and alternatives12 min read
- React Onboarding Flow: Slides, Permissions, Install
Build it: screens and apps9 min read
- React PWA Install Prompt for Chrome, Edge and iPhone
Mobile UI in React9 min read
