Features

Live activities

A live activity shows a running task — a delivery, a timer, a download — outside the app.

PlatformSurface
iOS 16.2+Live Activity on the Lock Screen and in the Dynamic Island
Android 8+Ongoing notification; Android 16 promotes it to a Live Update chip when the user allows
macOS / Windows / LinuxDynamic-Island pill at the top centre (around the notch on notched Macs); expands on hover
On desktop the activity is a Dynamic-Island pill: compact, expanded on hover (built-in renderer).
import { startActivity, updateActivity, endActivity } from "tauri-plugin-widgets-api";

await startActivity({
  id: "order.42",
  title: "Order #42",
  body: "Driver en route",
  icon: "bicycle",
  progress: 0.3,
  endsAt: Date.now() + 12 * 60_000,
  actions: [{ id: "call", label: "Call driver" }],
});

await updateActivity({ id: "order.42", title: "Order #42", body: "Arriving", progress: 0.9 });
await endActivity("order.42", 5_000);

Without layout, the plugin builds one from title, body, icon, progress, endsAt and actions, so a plain request looks alike everywhere. Pass layout (any widget element) for your own design, and compactLeading / compactTrailing / minimal for the Dynamic Island. The request must stay under 4 KiB of JSON — the ActivityKit limit, checked on every platform so nothing works on desktop and fails on an iPhone. The check counts the request including the layout the plugin generates when layout is omitted.

Taps on actions (Android buttons, buttons in your layout) arrive through onWidgetAction with widgetId: "activity.<id>". url opens a deep link on tap.

iOS also reads relevanceScore (which activity the system shows first), alert ({ title, body, sound? }, lights the screen with the update) and timestamp (Unix ms; an update older than the shown one is dropped). endActivity(id, dismissAfterMs, { content, dismissalPolicy }) ends with a final state; dismissalPolicy: "immediate" removes it at once. onActivityState / onActivityContent report what the OS did (ended, dismissed, push-updated).

Templates: data-only updates

Store the layout on the device once, then send only data. The update stays far below 4 KiB, which matters for frequent and push updates.

import { registerActivityTemplate, startActivity, updateActivity } from "tauri-plugin-widgets-api";

await registerActivityTemplate("delivery", {
  title: "Order {{data.order}}",
  lockScreen: {
    type: "vstack",
    children: [
      { type: "text", content: "{{data.driver}} arrives in {{data.eta}} min" },
      { type: "progress", value: 0, bind: { value: "data.progress" } },
      { type: "text", content: "Running late", when: { path: "data.late" } },
    ],
  },
  compactTrailing: { type: "text", content: "{{data.eta}}′", transition: "numeric" },
});

await startActivity({ id: "order.42", template: "delivery", data: { order: "#42", driver: "Ann", eta: 12, progress: 0.2 } });
await updateActivity({ id: "order.42", template: "delivery", data: { order: "#42", driver: "Ann", eta: 4, progress: 0.8 } });

A template takes the ActivityContent fields (title, layout, lockScreen, the island regions, compactLeading / compactTrailing / minimal, …). Every string in it is resolved with data like a widget config: {{data.x}}, bind, when, repeat. Fields set on the request itself override the template's (title may then be omitted). The template is kept in the shared store (activity-template:{name}, App Group on Apple); registerActivityTemplate(name, null) removes it. Only the request as sent — template name plus data — counts toward 4 KiB. Starting with an unknown template fails on desktop; on iOS / Android the renderer shows what the request itself carries.

Setup

iOS. Add TauriLiveActivity() to the extension's WidgetBundle (the init-ios template does) and NSSupportsLiveActivities = YES to the app's Info.plist (init-ios adds it to project.yml). With push: true the result has pushToken and an activity-token event follows when it changes — send it to your APNs server.

Android. On Android 13+ the app needs the POST_NOTIFICATIONS permission at runtime (for example requestPermission() of @tauri-apps/plugin-notification). The plugin declares it in its manifest.

Desktop. The island uses the built-in renderer; allow its window label in your capability ("windows": ["main", "activity-*"]). On macOS enable the plugin feature macos-private-api (and app.macOSPrivateApi: true) for the transparent, rounded pill. An id started again while its endActivity(id, dismissAfterMs) delay runs stays on.

Dynamic Island

On iPhone and on desktop the activity shows in a Dynamic-Island pill with compact, minimal and expanded regions; see Dynamic Island for the regions, desktop placement and recordings.

Push updates (iOS)

  • startActivity({ …, push: true }) returns pushToken; rotations arrive through onActivityTokens (kind: "activity"). Send ActivityKit pushes (event: "update" / "end") with content-state: { "json": "<ActivityContent JSON>" }.
  • Push-to-start (iOS 17.2+): getActivityPushToStartToken() and onActivityTokens (kind: "pushToStart"). Your server starts an activity with event: "start", attributes-type: "TauriActivityAttributes", attributes: { "id": "...", "group": "group.…" } and the same content-state. The json must contain a filled layout (send what startActivity would send) or a registered template with data, at most 4 KiB.
  • Android and desktop return null for push-to-start.

iOS: lifecycle, relevance, alerts, final content

  • Events. Every activity the app sees (started here, in an earlier launch, or by push-to-start) reports activity-state { id, state } (active, stale, ended, dismissed, pending) — the user can end it from the Lock Screen — and activity-content { id, content } when its content changes, pushes included.
  • relevanceScore (number) decides which activity the Dynamic Island shows when several run (higher first).
  • alert: { title, body, sound? } on updateActivity lights the screen and shows the alert; sound is "default" or a sound file in the app bundle.
  • timestamp (Unix ms): an update older than the last one applied is dropped (applied: false), so late network responses cannot roll the activity back. On iOS 17.2+ it is also passed to ActivityKit.
  • End with final content. endActivity accepts the final content (shown until dismissed) and dismissalPolicy: "default" | "immediate" (dismissAfterMs wins when set; without either it is dismissed at once, as before).
  • Frequent updates. init-ios also sets NSSupportsLiveActivitiesFrequentUpdates, which raises the hourly budget for push and local updates.