Device
Share sheets, secure storage, deep links, push, lifecycle, haptics, biometrics and the camera as Alpine magics that fall back to the web.
Overview
Each magic talks to Capacitor inside a Capacitor app and to a web equivalent, or an honest no-op, in a browser. The same markup runs as a website, an installed PWA and a packaged app.
<span x-text="$device.online ? 'Online' : 'Offline'"></span>
<span x-text="$device.standalone ? 'Installed' : 'In a browser tab'"></span>
<span x-text="$device.os"></span>
Setup
Device is opt-in. It loads on its own when a page uses one of its magics, when manifest.json has a device block, or inside a Capacitor app. The + prefix keeps the default plugins and adds it explicitly. native still works as an alias in both places.
<script src="https://cdn.jsdelivr.net/npm/mnfst@latest/lib/manifest.min.js"
data-plugins="+device"></script>
Capacitor plugins are read from window.Capacitor.Plugins. Install the ones you use when packaging the app.
$device
$device is part of the core utilities and always available. This plugin only adds Capacitor's network and platform readings to it.
| Property | Value |
|---|---|
$device.os |
macos, windows, linux, ios, android |
$device.platform |
web, ios, android: the container, not the OS |
$device.online |
Connectivity, reactive |
$device.standalone |
Installed as a PWA |
$device.native |
Inside a Capacitor app |
$device.touch |
Coarse pointer |
<p x-show="!$device.online">You're offline. Changes sync when you're back.</p>
<button x-show="$device.native">Pay with Face ID</button>
<button x-show="!$device.native">Pay</button>
The same state is stamped on <html> as data-os, data-platform, data-online, data-standalone and data-native, which drive the .native-only, .web-only, .offline-only, .online-only and .standalone-only classes and the OS classes.
$share
$share() opens the native share sheet inside a Capacitor app (Share) and the Web Share sheet in a browser. Without Web Share it copies the URL, or the text, to the clipboard. It resolves { shared, method, cancelled? }; method is native, web, clipboard or none.
<button @click="$share({ title: 'Manifest', text: 'Supercharged HTML', url: 'https://manifestx.dev' })">Share</button>
$camera
photo() captures and pick() chooses from the library (Camera). Both resolve { dataUrl, format }, or { cancelled: true }. On the web both open a file picker; on a phone photo() asks for the camera.
<div x-data="{ shot: null }">
<button @click="shot = await $camera.photo()">Take a photo</button>
<img x-show="shot?.dataUrl" :src="shot?.dataUrl" alt="">
</div>
$app
$app.active is true in the foreground. Capacitor reports appStateChange (App); the web uses visibilitychange, pageshow and pagehide. $app.onChange(fn) runs on each transition.
<div x-data="{ away: 0 }" x-init="$app.onChange(active => { if (!active) away++ })">
<span x-text="$app.active ? 'Active' : 'In the background'"></span>
<span x-text="away + ' times away'"></span>
</div>
$secure
Key/value storage. Inside a Capacitor app it uses the Keychain or Keystore through a plugin registered as SecureStorage. On the web it is localStorage under an mnfst: prefix, which is not encrypted. get() resolves a string or null.
<div x-data="{ value: null }">
<button @click="await $secure.set('token', 'abc123')">Set</button>
<button @click="value = await $secure.get('token')">Get</button>
<button @click="await $secure.remove('token'); value = null">Remove</button>
<code x-text="JSON.stringify(value)"></code>
</div>
keys() lists stored keys and clear() removes them all. If your plugin's method names differ, hand it over once with $secure.use(adapter).
$haptics
impact(), notification() and selection() map to Capacitor Haptics. The web falls back to navigator.vibrate, which desktop browsers and iOS Safari ignore.
<button @click="$haptics.impact('LIGHT')">Light</button>
<button @click="$haptics.impact('HEAVY')">Heavy</button>
<button @click="$haptics.notification('SUCCESS')">Success</button>
impact takes LIGHT, MEDIUM (default) or HEAVY; notification takes SUCCESS (default), WARNING or ERROR. vibrate(ms) is a plain buzz.
$links
Inside a Capacitor app (App) a tapped universal link like https://app.example.com/order/123, or a custom scheme like myapp://order/123, is routed to /order/123 through the router. $links.open(url) runs the same path by hand; $links.last keeps the latest inbound URL.
<button @click="$links.open('myapp://docs/core-plugins/device')">Open a deep link</button>
<code x-text="$links.last"></code>
$links.on(fn) replaces the default routing; fn receives { url, path }.
$push
$push.permission is prompt, granted, denied or unsupported. Nothing is asked until you call $push.request(), which prompts and resolves the new state.
<span x-text="$push.permission"></span>
<button @click="$push.request()">Enable notifications</button>
Inside a Capacitor app (PushNotifications), register() registers with APNs or FCM, onToken(fn) receives the device token for your backend, onReceive(fn) fires in the foreground, and a tapped notification routes to its url or route unless onTap(fn) takes over. On the web, permission maps to the Notification API; register() is a no-op and token stays null.
$biometric
available() resolves true when a Capacitor biometric plugin (BiometricAuth or NativeBiometric) reports Face ID or Touch ID. verify({ reason }) resolves { verified: true }, or { verified: false, error }. The web has no equivalent: available() is false and verify() reports unsupported.
<div x-data="{ can: null }" x-init="can = await $biometric.available()">
<button x-show="can" @click="$biometric.verify({ reason: 'Confirm payment' })">Pay with Face ID</button>
<span x-show="can === false">Biometrics are not available here.</span>
</div>
Reference
| Magic | API | Web | Capacitor |
|---|---|---|---|
$device |
os, platform, online, standalone, native, touch |
Always on (core) | Network |
$share(opts) |
{ shared, method, cancelled? } |
Web Share, then clipboard | Share |
$camera |
photo(), pick() |
File picker | Camera |
$app |
active, onChange(fn) |
Page visibility | App |
$secure |
get, set, remove, keys, clear, use(adapter) |
Prefixed localStorage |
SecureStorage |
$haptics |
impact, notification, selection, vibrate |
navigator.vibrate |
Haptics |
$links |
open(url), on(fn), last |
open() only |
App |
$push |
permission, token, request, register, onToken, onReceive, onTap |
Permission only | PushNotifications |
$biometric |
available(), verify(opts) |
false / unsupported |
BiometricAuth, NativeBiometric |
Related
- Native Apps — packaging with Capacitor
- Router — where deep links and notification taps land
- Utilities — OS classes and variants
Article does not exist
There is no documentation at this path.