FRAMEWORKExperimental — not for production

Menus and shortcuts

Owned native menus, portable accelerators, command routing, and tray items.

Experimental source documentation

These guides describe the current source checkout. The published preview predates the recent API changes. See release status before choosing an SDK.

Spark menu items discriminate type, use stable IDs, and share label/disabled/checked semantics. Every surface accepts its own subset; shared types do not imply all native surfaces support submenus, roles, icons, or sliders.

Application menus

import { createMenu } from '@legendapp/spark/menus';

const menu = await createMenu({
  id: 'editor',
  items: [{
    type: 'submenu', id: 'file', label: 'File', target: { menu: 'file' },
    items: [{ type: 'action', id: 'open', label: 'Open…', shortcut: 'CmdOrCtrl+O' }],
  }],
  onAction: event => { console.log(event.itemId); },
});
await menu.update({ items: [] });
await menu.remove();

Top-level items are submenus. Contributions merge by IDs, never translated labels. Later owners take precedence; removal restores earlier contributions/native items. Updates replace the owner's contribution. Native publication is acknowledged before create/update resolves. Failed publication retains the previous state; failed removal retains retryable ownership.

Targets bind native root menus, IDs, or macOS semantic roles. Role items dispatch through native responders; ordinary actions targeting roles dispatch to JavaScript. useMenu owns the same lifetime and exposes loading/ready/error state plus cleanup-error callbacks. Keep unchanged item structure stable; callback changes do not require republishing.

Surface support

SurfaceSupported item shapesLimits
App menuRecursive actions, checkboxes, separators, submenus; macOS rolesWindows role/icon requests reject
Context menuFlat actions, checkboxes, separatorsNo submenus, roles, icons, shortcuts, or sliders
Tray menuActions, checkboxes, separators, recursive submenusNo roles, icons, shortcuts, or sliders
macOS toolbar popupActions, checkboxes, separators, numeric slidersNo submenus, roles, or shortcuts
macOS DockRecursive actions, checkboxes, separators, submenusRestricted presentation/targeting fields
Windows taskbarFlat actions and checkboxesDisabled entries omitted; restricted fields

showContextMenu({ windowId, position, items }) requires an explicit owner. Position is logical top-left content coordinates, rather than display/outer-frame coordinates. Cancellation returns { canceled: true }; selection returns { canceled: false, itemId }. A concurrent popup rejects E_BUSY.

createTray returns a handle with update and async remove. Images distinguish SF Symbol names from local files; symbol images are macOS-specific. Dock/taskbar menu registrations live in /system and have separate native availability.

Local and global shortcuts

import { registerShortcut } from '@legendapp/spark/shortcuts';

const shortcut = await registerShortcut('CmdOrCtrl+S', () => {
  console.log('Save requested');
}, { windowId: 'main' });
await shortcut.remove();

Local and global registration share portable accelerator parsing and async removal. Local options include window ownership and repeat behavior. registerGlobalShortcut(accelerator, handler, { repeat? }) lives under /global-shortcuts. Windows supports repeat; requesting it on macOS rejects E_UNSUPPORTED_OPTION. Conflicts are failures, not silently replaced registrations.

Commands and raw keys

/shortcuts/commands includes createHotkeyRouter, createHotkeyStore, optional React bindings, capture, and settings UI. Routers arbitrate enabled commands by window/application scope and priority. The store owns versioned JSON bindings, keeps portable CmdOrCtrl spelling, exposes conflicts, and requires flush/close for persistence. Its /storage alias was removed.

/shortcuts/keyboard supplies addKeyboardListener, key codes, and modifier helpers. Registrations have explicit ownership; callbacks observe native consumed/captured decisions and cannot consume a key by returning a value. The low-level backend/command router currently requires macOS. Native shortcut matching never waits synchronously for JavaScript.

See menu details and command routing.