Murasaki

App Menu

Define the native application menu bar with useAppMenu — NSMenu on macOS, HMENU on Windows.

useAppMenu declares your app's menu bar — the menu at the top of the screen on macOS, and the window's menu bar on Windows. Like useContextMenu, it's a hook, not markup: items are posted to the Rust side, which builds a real OS menu (NSMenu / HMENU). It's the Murasaki equivalent of Electron's Menu.setApplicationMenu or Tauri's menu API.

It's opt-in. Without it, your app still gets Murasaki's standard menu (an app menu with About/Quit, plus Edit and Window). Call useAppMenu only when you want your own. Because the application menu is process-global, only the primary main renderer may replace it, and that renderer needs menu:application:

murasaki.config.ts
window: {
  capabilities: [
    'menu:application',
    'window:close',
    'clipboard:readText',
    'clipboard:writeText',
  ],
}

Native roles also require their matching capabilities. The list above covers both { role: 'close' } and the complete { role: 'editMenu' } submenu in the example.

Declaring a menu

Call it once in your root layout. A menu is either a { label, items } group or a standard { role } submenu; each item is a custom entry, a standard { role }, or a divider:

src/app/layout.tsx
import { useAppMenu, Action } from 'murasaki'

useAppMenu([
  {
    label: 'File',
    items: [
      { label: 'New Window', shortcut: 'command,N', action: () => openWindow() },
      { separator: true },
      { role: 'close' },
    ],
  },
  { role: 'editMenu' },
  {
    label: 'View',
    items: [{ label: 'Reload', shortcut: 'command,R', action: <Action.Reload /> }],
  },
])

On macOS the standard app-name menu (About / Hide / Quit) is always added ahead of your menus, so Cmd+Q and friends keep working — you don't declare it yourself.

Roles

A role pulls in a standard item or submenu: it's localized for you and wired to the native behavior (via NSMenu's responder chain on macOS), so you don't supply an action.

  • Item roles: quit, close, minimize, zoom, undo, redo, cut, copy, paste, selectAll, reload.
  • Menu roles: editMenu and windowMenu — the standard Edit and Window submenus in one line.

Use roles for anything standard (especially the Edit items — copy/paste need native behavior to reach the focused field), and custom entries with an action for the rest.

Every custom application menu requires menu:application. Add the following capabilities for the native roles you include:

RoleAdditional capability
quitapp:quit
closewindow:close
minimizewindow:minimize
zoomwindow:toggleMaximize
cut, copyclipboard:writeText
pasteclipboard:readText
editMenuBoth clipboard:readText and clipboard:writeText
windowMenuBoth window:minimize and window:toggleMaximize
undo, redo, selectAll, reloadNone beyond menu:application

If any required capability is missing, Murasaki rejects the entire replacement and keeps the current application menu instead of installing a partially working menu.

Shape

type AppMenu =
  | { label: string; items: AppMenuItemSpec[] }
  | { role: 'editMenu' | 'windowMenu' }

type AppMenuItemSpec = AppMenuEntry | { role: AppMenuItemRole } | { separator: true }

interface AppMenuEntry {
  label: string
  shortcut?: string          // e.g. "command,N"
  disabled?: boolean
  action?: AppMenuAction     // a built-in <Action.*/> element, or a function
  items?: AppMenuItemSpec[]  // a submenu, instead of an action
}

Item shape and action are the same as useContextMenu: action is your own function or a built-in <Action.*/>, and shortcut sets the native accelerator label. Reuse your app's actions with createActions.

Mount useAppMenu once in the primary layout. Calls from secondary windows are ignored so a hidden renderer cannot replace process-global native chrome. Not calling it keeps Murasaki's default menu.

Platform notes

  • macOS — a top-of-screen NSMenu; the app-name menu is always prepended.
  • Windows — an in-window HMENU bar; it inherits the OS's classic styling and doesn't follow dark mode. The close role hides a secondary so it can be reopened; closing the primary main window and quitting enter the app lifecycle.
  • Linux — no default menu bar yet.

Next

Improve this page on GitHub

On this page