Menus
Perry supports native menu bars, context menus, and toolbars across all
platforms. Every snippet below is excerpted from
docs/examples/ui/menus/snippets.ts —
CI compiles and runs it on every PR.
The menu API is handle-based and free-function: build menus with
menuCreate(), fill them with menuAddItem / menuAddItemWithShortcut, and
attach them with menuBarAddMenu(bar, title, menu). Submenus go through
menuAddSubmenu(parent, title, submenu).
Menu Bar
// Menus are created independently, then attached. Build child menus first,
// then hand them to `menuBarAddMenu(bar, title, menu)`.
const menuBar = menuBarCreate()
// File menu
const fileMenu = menuCreate()
menuAddItemWithShortcut(fileMenu, "New", "n", () => status.set("file/new"))
menuAddItemWithShortcut(fileMenu, "Open…", "o", () => status.set("file/open"))
menuAddSeparator(fileMenu)
menuAddItemWithShortcut(fileMenu, "Save", "s", () => status.set("file/save"))
menuAddItemWithShortcut(fileMenu, "Save As…", "S", () => status.set("file/saveAs"))
menuBarAddMenu(menuBar, "File", fileMenu)
// Edit menu
const editMenu = menuCreate()
menuAddItemWithShortcut(editMenu, "Undo", "z", () => status.set("edit/undo"))
menuAddItemWithShortcut(editMenu, "Redo", "Z", () => status.set("edit/redo"))
menuAddSeparator(editMenu)
menuAddItemWithShortcut(editMenu, "Cut", "x", () => status.set("edit/cut"))
menuAddItemWithShortcut(editMenu, "Copy", "c", () => status.set("edit/copy"))
menuAddItemWithShortcut(editMenu, "Paste", "v", () => status.set("edit/paste"))
menuBarAddMenu(menuBar, "Edit", editMenu)
// Submenu: View → Zoom
const viewMenu = menuCreate()
const zoomSubmenu = menuCreate()
menuAddItemWithShortcut(zoomSubmenu, "Zoom In", "+", () => status.set("zoom/in"))
menuAddItemWithShortcut(zoomSubmenu, "Zoom Out", "-", () => status.set("zoom/out"))
menuAddItemWithShortcut(zoomSubmenu, "Actual Size", "0", () => status.set("zoom/reset"))
menuAddSubmenu(viewMenu, "Zoom", zoomSubmenu)
menuBarAddMenu(menuBar, "View", viewMenu)
menuBarAttach(menuBar)
Menu Bar Functions
| Function | Description |
|---|---|
menuBarCreate() | Create a new (empty) menu bar |
menuCreate() | Create a new menu — used as a child of the bar or as a submenu |
menuBarAddMenu(bar, title, menu) | Attach a top-level menu under title |
menuAddItem(menu, label, callback) | Append an item without a shortcut |
menuAddItemWithShortcut(menu, label, shortcut, callback) | Append an item with a keyboard shortcut |
menuAddSeparator(menu) | Append a horizontal separator line |
menuAddSubmenu(parent, title, submenu) | Nest a previously-created menu under a label |
menuBarAttach(bar) | Install the bar as the application’s main menu |
Keyboard Shortcuts
The third argument to menuAddItemWithShortcut is the shortcut key:
| Shortcut | macOS | Other |
|---|---|---|
"n" | Cmd+N | Ctrl+N |
"S" | Cmd+Shift+S | Ctrl+Shift+S |
"+" | Cmd++ | Ctrl++ |
Uppercase letters imply Shift.
Context Menus
Right-click menus are attached to widgets via widgetSetContextMenu(widget, menu).
Build the menu the same way as a menu-bar entry, then bind it:
const label = Text("Right-click me")
const ctx = menuCreate()
menuAddItem(ctx, "Copy", () => status.set("ctx/copy"))
menuAddItem(ctx, "Paste", () => status.set("ctx/paste"))
menuAddSeparator(ctx)
menuAddItem(ctx, "Delete", () => status.set("ctx/delete"))
widgetSetContextMenu(label, ctx)
Toolbar
Add a toolbar to a window. toolbarAddItem takes an identifier (used by
AppKit to deduplicate items) and a label:
const toolbar = toolbarCreate()
toolbarAddItem(toolbar, "new", "New", () => status.set("tb/new"))
toolbarAddItem(toolbar, "save", "Save", () => status.set("tb/save"))
toolbarAddItem(toolbar, "run", "Run", () => status.set("tb/run"))
// `toolbarAttach(toolbar, window)` mounts onto a specific window.
const win = Window("Toolbar Demo", 800, 600)
toolbarAttach(toolbar, win as unknown as number)
Platform Notes
| Platform | Menu Bar | Context Menu | Toolbar |
|---|---|---|---|
| macOS | NSMenu | NSMenu | NSToolbar |
| iOS | — (no menu bar) | UIMenu | UIToolbar |
| Windows | HMENU/SetMenu | — | Horizontal layout |
| Linux | GMenu/set_menubar | — | HeaderBar |
| Web | DOM | DOM | DOM |
iOS: Menu bars are not applicable. Use toolbar and navigation patterns instead.