Tutorial: a tray status app

The single biggest thing an installed app can do that a browser tab can't: keep working after the window closes, one click away in the menu bar (macOS) or system tray (Windows). This tutorial turns your app into that, in about ten minutes, using a dashboard-defined tray plus a few lines of SDK code.

What you'll ship: your icon in the status area, a menu under it, the app minimizing to the tray instead of quitting, and a live status number on the tray itself.

A Deskifier tray icon with its menu, open in the real macOS menu bar

1. Define the tray in the dashboard

Open your app's Trays page and add a tray. Give it a memorable slug (the default main is fine) and either upload a dedicated icon or let it use your app icon. On macOS, enable Adapt to the macOS menu bar so the icon renders as a template image in light and dark menu bars.

The Trays page in the Deskifier dashboard

A dashboard-defined tray with spawn on start appears as soon as the app launches, before any of your code runs. (You can also create trays at runtime with tray.create, but a config-defined tray survives even when your page hasn't loaded.)

2. Keep the app alive when the window closes

The tray is only useful if closing the window doesn't quit the app. Add a behavior on the Behaviors page:

  • When: window is closing
  • Then: Minimize to tray

And a second one so the tray brings the window back:

  • When: the tray icon is clicked
  • Then: Show window, Focus window

The Behaviors page with tray-related rules configured

That's the whole retention loop with zero code: close hides to tray, click restores.

3. Drive the tray from your web app

Everything above is configuration. The SDK makes the tray live. For example, show an unread count next to the icon:

// Anywhere in your web app, whenever the count changes:
await window.deskifier.tray.setTitle({
  id: 'main',
  title: unreadCount > 0 ? String(unreadCount) : '',
});

Or react to clicks yourself instead of (or in addition to) the behavior:

window.deskifier.tray.onClick(() => {
  // e.g. toggle a mini window, refresh data, etc.
});

Attach a richer menu at runtime with menus.createTray if the static one isn't enough.

4. Guard for the browser

Your app still runs in browsers, where window.deskifier doesn't exist. Feature-detect once and branch:

const isDesktop = typeof window !== 'undefined' && 'deskifier' in window;
if (isDesktop) {
  await window.deskifier.tray.setTitle({ id: 'main', title: '3' });
}

Where to go next