# Menus and shortcuts

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

Source: https://legend.so/spark/menus/

Create menus with typed items and stable IDs. Each surface supports the item types listed below.

## [Application menus](https://legend.so/spark/menus/#application-menus)

```ts
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.

## [Checked items](https://legend.so/spark/menus/#checked-items)

Use `type: 'checkbox'` with a boolean `checked` for selectable state. The shared contract also defines `radio`, but the surfaces below reject it with `E_UNSUPPORTED_OPTION`; `/menus` excludes it from its exported `MenuItem` type.

## [Surface support](https://legend.so/spark/menus/#surface-support)

| Surface | Supported item shapes | Limits |
| --- | --- | --- |
| App menu | Recursive actions, checkboxes, separators, submenus; macOS roles | Windows role/icon requests reject |
| Context menu | Flat actions, checkboxes, separators | No submenus, roles, icons, shortcuts, or sliders |
| Tray menu | Actions, checkboxes, separators, recursive submenus | No roles, icons, shortcuts, or sliders |
| macOS toolbar popup | Actions, checkboxes, separators, numeric sliders | No submenus, roles, or shortcuts |
| macOS Dock | Recursive actions, checkboxes, separators, submenus | Restricted presentation/targeting fields |
| Windows taskbar | Flat actions and checkboxes | Disabled 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.

## [Shortcuts](https://legend.so/spark/menus/#shortcuts)

Set an item's `shortcut`, such as `CmdOrCtrl+O`, to show and register its accelerator. Use [Keyboard shortcuts](https://legend.so/spark/keyboard-shortcuts/) for local/global registrations, named commands, customizable bindings, and raw key events.

See [menu details](https://github.com/LegendApp/legend-spark/blob/main/docs/menus.md) for targeting and ownership.

[View Markdown](https://legend.so/spark/menus.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Menus+and+shortcuts&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Fmenus%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A)
