Dynamic Island
A running live activity shows in the Dynamic Island: on an iPhone the system island, on desktop a pill the plugin draws at the top centre of the screen — around the camera notch on a notched Mac. The same request drives both, with the same regions.
Start one
import { startActivity, updateActivity, endActivity } from "tauri-plugin-widgets-api";
await startActivity({
id: "timer",
title: "Focus",
icon: "timer",
endsAt: Date.now() + 25 * 60_000, // compact trailing shows the countdown
compactLeading: { type: "image", systemName: "brain.head.profile", color: "#a78bfa" },
compactTrailing: { type: "timer", targetDate: new Date(Date.now() + 25 * 60_000).toISOString(), counting: "down" },
islandCenter: { type: "text", content: "Deep work", fontWeight: "semibold" },
layout: { type: "progress", value: 0.1, tint: "#a78bfa" },
keylineTint: "#a78bfa",
});
await updateActivity({ id: "timer", layout: { type: "progress", value: 0.6, tint: "#a78bfa" } });
await endActivity("timer", 3_000);
Without the region fields the plugin fills them from title, icon, progress and
endsAt, so a plain startActivity already gets a sensible pill.
Regions
| State | Shows | iPhone | Desktop |
|---|---|---|---|
| Compact | compactLeading and compactTrailing either side of the camera (default: icon, and the endsAt countdown or progress %) | yes | yes |
| Minimal | other running activities as small circles (minimal, default icon); a click brings one to the pill | yes | yes |
| Expanded | islandLeading / islandTrailing in the top band, islandCenter under it, layout at the bottom | long press | hover or click |
| Lock Screen | lockScreen (else layout) | yes | — |
keylineTint outlines the pill. Every region is an ordinary widget element: text, timers,
progress, images, stacks, buttons.
Taps and a launcher
Elements with action or url work in every region. Taps arrive like widget taps, with
widgetId: "activity.<id>":
import { onWidgetAction, createWidgetWindow } from "tauri-plugin-widgets-api";
await onWidgetAction(async ({ widgetId, action }) => {
if (widgetId === "activity.timer" && action === "open") {
// Open a small window under the island, like a launcher.
await createWidgetWindow({ label: "launcher", widgetId: "launcher", size: "medium", y: 48 });
}
});
Desktop placement
plugins.widgets.activitySurface picks the desktop surface: "island" (default),
"window" (the older corner window) or "notification" (Linux / Windows, feature
notify). The island is one borderless window per app.
| Platform | Placement and window |
|---|---|
| Mac with a notch (Apple silicon) | pill as tall as the notch, wrapping it (NSScreen.safeAreaInsets, auxiliaryTopLeftArea / auxiliaryTopRightArea); window level above the menu bar, on every Space and over full-screen apps |
| Mac without a notch | floating pill just under the menu bar |
| Windows | top-centre topmost tool window, no taskbar button, does not take focus |
| Linux X11 | top-centre DOCK window above other windows |
| Linux Wayland | with feature layer-shell: an Overlay layer surface anchored to the top edge; without it an ordinary always-on-top window wherever the compositor puts it |
The window is always exactly the visible shapes, so clicks next to the pill reach the windows below. Hover is read from the global cursor position (a webview in an inactive macOS app gets no mouse-enter events); where that is unavailable (Wayland), the page's own hover and a click expand it, and a click on the top band collapses it.
Setup
- Desktop. Allow the island window label in your capability (
"windows": ["main", "activity-*"]). On macOS enable the plugin featuremacos-private-apiandapp.macOSPrivateApi: truefor the transparent, rounded pill. - iOS. Add
TauriLiveActivity()to the extension'sWidgetBundleandNSSupportsLiveActivities = YESto the app's Info.plist (init-iosdoes both); see Live activities.
The whole request stays under 4 KiB of JSON (the ActivityKit limit), checked on every platform.