SwAddToHomescreenGuide
When mobile users first visit a PWA project, a non-blocking info card slides up from the bottom of the screen, guiding them to save the site to their home screen. iOS Safari and major Android Chromium browsers (Chrome / Edge / Samsung Internet / Firefox / Opera) each get tailored step instructions; other environments (desktop / WebView / non-Safari iOS / regional browsers) stay silent to avoid misleading users.
The component uses <Teleport to="body"> + <Transition> to render as a bottom sheet,
with a translucent backdrop (--sw-add-to-homescreen-guide-backdrop, default
rgb(0 0 0 / 45%)) but no scroll lock and no focus trap — users can keep browsing;
dismissal is via the top-right X button, clicking the backdrop, or the Escape key.
Trigger Conditions
All conditions below must be met to display:
- Client-side environment (does not render during SSR)
- PWA is enabled — the runtime predicate is
runtimeConfig.public.swiftWebFramework.pwa.enabled === true, derived from the config-timeframework.pwa(a boolean) inswift.config.json framework.addToHomescreen.enabled !== false- Not in standalone display mode, i.e. the user has not added the site to their home screen yet. Both checks must pass (fw#352):
window.matchMedia('(display-mode: standalone)').matches === falseandnavigator.standalone !== true— the latter is iOS-only and is the sole reliable signal for an iOS legacy web clip (what "Add to Home Screen" produces when no manifest is linked), where the media query does not match - Device platform unambiguously detected (see UA matrix below)
- User has not been "remembered as dismissed" recently (controlled by
rememberFor) - No modal dialog is open when the delay elapses (fw#387): the predicate is
document.body.style.pointerEvents === 'none'— while a reka-ui Dialog (what SwModal is built on) is open it setspointer-events: noneon body, so even though the guide layer sits above it visually, its X and backdrop cannot be tapped. When hit, the guide rechecks every 500ms and opens once the dialog closes. Typical case: a business age-confirmation or announcement modal appearing together with the guide on first visit
Warning when PWA disabled: in dev mode, if the component is mounted but the runtime
pwa.enabled === false, a console warn is logged advising to either enable PWA or remove the mount fromapp.vue.
Device / Browser Support Matrix
| Platform | Browser | Triggers | Notes |
|---|---|---|---|
| iOS | Safari | ✓ | iOS 16+ / 17+ / 26+ |
| iOS | Chrome / Firefox / Edge | ✗ | iOS only allows Safari to add to home screen |
| iOS | WeChat / other WebView | ✗ | WebView has no native entry |
| Android | Chrome | ✓ | Mainstream entry |
| Android | Edge / Samsung Internet | ✓ | Also supported |
| Android | Firefox / Opera | ✓ | Major Chromium derivatives |
| Android | WebView (; wv)) | ✗ | WebView has no native entry |
| Android | UC / QQ / WeChat / Baidu / MIUI / Vivo / Oppo / Quark / 360 / Sogou | ✗ | Regional browsers lack PWA entry or have poor UX |
| Desktop (macOS / Windows / Linux) | any | ✗ | Mobile-only guide |
Basic Usage
Demo count exemption: the full UI only appears on iOS Safari / Android Chromium with all five gates passing; desktop docs cannot demonstrate more forms (slot customization needs a real device). Two runnable demos + the gate probe below.
Mount demo (visible on mobile browsers)
Gate probe (why it does not show)
The framework ships with a plain-text fallback — drop-in ready:
<template>
<NuxtLayout><NuxtPage /></NuxtLayout>
<SwModalHost />
<SwToast />
<SwAddToHomescreenGuide />
</template>
Mount it in app.vue next to <SwModalHost /> and <SwToast />. The framework slides in the guide
2 seconds after mount (default), with two-step iOS or Android instructions.
Injecting an Icon Library
The framework does not bundle any icon SVGs (per the "no icon library" rule). Appliers are expected to
inject icons via slots. Real-world example with @phosphor-icons/vue:
<script setup lang="ts">
import { PhShareNetwork, PhDotsThreeVertical, PhPlusSquare } from '@phosphor-icons/vue'
</script>
<template>
<SwAddToHomescreenGuide>
<template #ios-instructions>
<li>
<PhShareNetwork :size="20" weight="regular" />
Tap the <strong>Share</strong> button in Safari's toolbar
</li>
<li>
<PhPlusSquare :size="20" weight="regular" />
Choose <strong>Add to Home Screen</strong> and confirm
</li>
</template>
<template #android-instructions>
<li>
<PhDotsThreeVertical :size="20" weight="regular" />
Tap the <strong>menu</strong> in the top-right of the browser
</li>
<li>
<PhPlusSquare :size="20" weight="regular" />
Choose <strong>Add to Home Screen</strong> and confirm
</li>
</template>
</SwAddToHomescreenGuide>
</template>
Controlling Frequency
By default, the guide shows once per browser session (resets when the tab closes).
Use the rememberFor prop for "don't pester for N days":
<!-- Don't show for a week after dismiss -->
<SwAddToHomescreenGuide remember-for="7d" />
<!-- Don't show for a day -->
<SwAddToHomescreenGuide remember-for="1d" />
<!-- Effectively forever (100 years) -->
<SwAddToHomescreenGuide remember-for="36500d" />
rememberFor value | Storage backend | Behavior |
|---|---|---|
'session' (default) | sessionStorage | Resets on tab close, reshows next visit |
| Positive number N | localStorage (with timestamp) | Don't show within N days after dismiss |
| 0 / negative | None | Show every visit (debug only) |
Customizing the Look
Tune via CSS variables on the applier side:
| Variable | Default | Controls |
|---|---|---|
--sw-add-to-homescreen-guide-bg | var(--popover) | Panel background |
--sw-add-to-homescreen-guide-accent | var(--primary) | Step number badge background |
--sw-add-to-homescreen-guide-accent-foreground | var(--primary-foreground) | Step number text colour (paired with the line above — do not change only one) |
--sw-add-to-homescreen-guide-backdrop | rgb(0 0 0 / 45%) | Backdrop colour |
--sw-add-to-homescreen-guide-step-bg | var(--muted) | Background of a single step row |
--sw-add-to-homescreen-guide-radius | 1.25rem | Top-corner radius |
--sw-add-to-homescreen-guide-z | 1100 | z-index |
--sw-add-to-homescreen-guide-close-bg | var(--muted) | Close button background (a soft circle so it reads as a button) |
--sw-add-to-homescreen-guide-close-hover-bg | var(--accent) | Close button hover background (only on hover: hover devices) |
--sw-add-to-homescreen-guide-shadow | 0 -8px 32px rgb(0 0 0 / 12%) | Panel shadow. Cast upward — it deliberately does not reuse --shadow-lg (those three are designed to cast downward) |
Class hook .sw-add-to-homescreen-guide (the root div portaled to body) allows deep override. Since the component
is teleported to body, scoped CSS does not reach it — use a non-scoped <style> block.
<style>
.sw-add-to-homescreen-guide {
--sw-add-to-homescreen-guide-accent: #ff6b00;
--sw-add-to-homescreen-guide-radius: 1.25rem;
border: 4px solid #000 !important;
box-shadow: 6px 6px 0 0 #000 !important;
}
</style>
Project-Level Configuration
Beyond per-component props, set project-level defaults via swift.config.json
(propagated through the framework module's runtimeConfig):
{ "framework": { "pwa": true } }
🔴 Writing
{ "pwa": { "enabled": true } }silently disables PWA. The framework's predicate is a strict=== true(module.ts); an object is falsy there, so@vite-pwa/nuxtis never installed — no service worker, no manifest, no add-to-home-screen path at all. The effect is identical to writing"pwa": false, with no error anywhere.sw config set framework.pwarejects the object form too (it is on the CLI's boolean-key list).
The guide's own behaviour goes in nuxt.config.ts (this option does not belong in
swift.config.json — the CLI does not manage it; it is framework component
behaviour, not manifest content):
swiftConfig: {
...swiftConfig.framework,
addToHomescreen: { delay: 3000, rememberFor: '7d' }, // enabled omitted = follows framework.pwa
},
enabledis optional — when omitted it followsframework.pwa(same as omitting the wholeaddToHomescreenblock), so{ delay: 3000 }is enough if you only want a different delay. Setenabled: falseexplicitly to keep PWA on while hiding the guide.
Priority: component prop > swiftConfig in nuxt.config.ts > built-in default ('session' / 2000ms).
To fully disable the guide (keep PWA but hide the prompt):
swiftConfig: { ...swiftConfig.framework, addToHomescreen: false },
Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
delay | number | 2000 | Milliseconds after mount before showing. Priority: prop > runtimeConfig > 2000 | |
rememberFor | 'session' | PeriodSpec | 'session' | Memory strategy. 'session' = sessionStorage; 'Nm'·'Nh'·'Nd' = N minutes·hours·days via localStorage; 0/negative = no memory |
Slots
| Slot | Default rendering | Description |
|---|---|---|
icon | The manifest's /icons/icon-192.png (hidden if it fails to load) | App icon |
title | Site name (project.title), falling back to "Save to Home Screen" | Modal title |
ios-instructions | iOS three steps (first two carry a built-in line SVG icon) | iOS Safari instructions content |
android-instructions | Android three steps (first two carry a built-in line SVG icon) | Android Chromium instructions content |
Troubleshooting
Guide doesn't appear? Check in this order:
- Is
framework.pwaset totrueinswift.config.json(a boolean, not an object)? Dev console will warn otherwise. Writing{ "enabled": true }silently disables PWA — see "Project-Level Configuration" above - First visit on the client side? Refreshing the same tab won't reshow (unless
rememberFor: 0) - Is the current UA in the support matrix? iOS Chrome / regional browsers / desktop never trigger
- Already added to home screen? After "Add to Home Screen", standalone mode skips the guide automatically
- Is
addToHomescreen.enabledset tofalse? Checkswift.config.json - In
rememberFor: Nmode: is localStorage'ssw:a2hs:dismissedAtstill within the N-day window? Clear browser storage or shortenrememberForto retrigger
No live demo: this component depends on a mobile UA + real PWA config. Desktop browsers can't trigger it, so the docs site does not provide a DemoBox. Test on a real device or use Chrome DevTools' "Toggle device toolbar" to simulate a mobile UA.
Caveats
- When "it never shows", walk the 5 silent gates first (Rule UAH-001) — most failed conditions
returnsilently with no message (only "PWA not enabled" emits a dev warning): ① PWA not enabled ②addToHomescreen.enabled === false③ already running standalone (debugging inside an installed PWA window means it will never show) ④ device is neither iOS Safari nor Android Chromium (ambiguous devices are always silent) ⑤ still inside a remembered quiet period. rememberForhas three modes and is the easiest thing to get wrong — a positiveN= don't show again for N days (persisted in localStorage);'session'= reset when the tab closes;0or negative = show every time (testing only). Wanting "show on every visit" but using'session'gets you once per session; wanting "show exactly once, forever" with'session're-shows after closing the tab.7is the common value.- iOS and Android render different copy and illustrations — the component branches on device, so UA emulation may not reach the branch you want to inspect.