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:
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:
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:
editMenuandwindowMenu— 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:
| Role | Additional capability |
|---|---|
quit | app:quit |
close | window:close |
minimize | window:minimize |
zoom | window:toggleMaximize |
cut, copy | clipboard:writeText |
paste | clipboard:readText |
editMenu | Both clipboard:readText and clipboard:writeText |
windowMenu | Both window:minimize and window:toggleMaximize |
undo, redo, selectAll, reload | None 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
HMENUbar; it inherits the OS's classic styling and doesn't follow dark mode. Thecloserole hides a secondary so it can be reopened; closing the primarymainwindow and quitting enter the app lifecycle. - Linux — no default menu bar yet.