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:

  1. Client-side environment (does not render during SSR)
  2. PWA is enabled — the runtime predicate is runtimeConfig.public.swiftWebFramework.pwa.enabled === true, derived from the config-time framework.pwa (a boolean) in swift.config.json
  3. framework.addToHomescreen.enabled !== false
  4. 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 === false and navigator.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
  5. Device platform unambiguously detected (see UA matrix below)
  6. User has not been "remembered as dismissed" recently (controlled by rememberFor)
  7. 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 sets pointer-events: none on 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 from app.vue.

Device / Browser Support Matrix

PlatformBrowserTriggersNotes
iOSSafari✓iOS 16+ / 17+ / 26+
iOSChrome / Firefox / Edge✗iOS only allows Safari to add to home screen
iOSWeChat / other WebView✗WebView has no native entry
AndroidChrome✓Mainstream entry
AndroidEdge / Samsung Internet✓Also supported
AndroidFirefox / Opera✓Major Chromium derivatives
AndroidWebView (; wv))✗WebView has no native entry
AndroidUC / 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 valueStorage backendBehavior
'session' (default)sessionStorageResets on tab close, reshows next visit
Positive number NlocalStorage (with timestamp)Don't show within N days after dismiss
0 / negativeNoneShow every visit (debug only)

Customizing the Look

Tune via CSS variables on the applier side:

VariableDefaultControls
--sw-add-to-homescreen-guide-bgvar(--popover)Panel background
--sw-add-to-homescreen-guide-accentvar(--primary)Step number badge background
--sw-add-to-homescreen-guide-accent-foregroundvar(--primary-foreground)Step number text colour (paired with the line above — do not change only one)
--sw-add-to-homescreen-guide-backdroprgb(0 0 0 / 45%)Backdrop colour
--sw-add-to-homescreen-guide-step-bgvar(--muted)Background of a single step row
--sw-add-to-homescreen-guide-radius1.25remTop-corner radius
--sw-add-to-homescreen-guide-z1100z-index
--sw-add-to-homescreen-guide-close-bgvar(--muted)Close button background (a soft circle so it reads as a button)
--sw-add-to-homescreen-guide-close-hover-bgvar(--accent)Close button hover background (only on hover: hover devices)
--sw-add-to-homescreen-guide-shadow0 -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/nuxt is 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.pwa rejects 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
},

enabled is optional — when omitted it follows framework.pwa (same as omitting the whole addToHomescreen block), so { delay: 3000 } is enough if you only want a different delay. Set enabled: false explicitly 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

NameTypeRequiredDefaultDescription
delaynumber2000Milliseconds 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

SlotDefault renderingDescription
iconThe manifest's /icons/icon-192.png (hidden if it fails to load)App icon
titleSite name (project.title), falling back to "Save to Home Screen"Modal title
ios-instructionsiOS three steps (first two carry a built-in line SVG icon)iOS Safari instructions content
android-instructionsAndroid three steps (first two carry a built-in line SVG icon)Android Chromium instructions content

Troubleshooting

Guide doesn't appear? Check in this order:

  1. Is framework.pwa set to true in swift.config.json (a boolean, not an object)? Dev console will warn otherwise. Writing { "enabled": true } silently disables PWA — see "Project-Level Configuration" above
  2. First visit on the client side? Refreshing the same tab won't reshow (unless rememberFor: 0)
  3. Is the current UA in the support matrix? iOS Chrome / regional browsers / desktop never trigger
  4. Already added to home screen? After "Add to Home Screen", standalone mode skips the guide automatically
  5. Is addToHomescreen.enabled set to false? Check swift.config.json
  6. In rememberFor: N mode: is localStorage's sw:a2hs:dismissedAt still within the N-day window? Clear browser storage or shorten rememberFor to 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 return silently 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.
  • rememberFor has three modes and is the easiest thing to get wrong — a positive N = don't show again for N days (persisted in localStorage); 'session' = reset when the tab closes; 0 or 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. 7 is 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.