> ## Documentation Index
> Fetch the complete documentation index at: https://webview.js.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Menu API — Native Menu Bars, Roles, and Shortcuts

> Build native menu bars with submenus, shortcuts, and predefined roles. Set app-level or per-window menus and handle clicks in Node.js.

WebviewJS uses [muda](https://github.com/tauri-apps/muda) to render fully native menu bars on each platform. Menus can be **global** (app-level, shared across all windows) or **per-window** (overriding the global menu for a specific window). All menu item click events are delivered to your Node.js process via the `custom-menu-click` application event.

***

## Setting a global menu

Call `app.setMenu(MenuOptions)` to attach a menu bar to the running application. The call is safe to make before or after windows are created.

```js theme={null}
import { Application } from '@webviewjs/webview';

const app = new Application();

app.setMenu({
  items: [
    {
      label: 'File',
      submenu: {
        items: [
          { id: 'file-new',  label: 'New',   accelerator: 'CmdOrCtrl+N' },
          { id: 'file-open', label: 'Open…', accelerator: 'CmdOrCtrl+O' },
          { role: 'separator' },
          { id: 'file-save', label: 'Save',  accelerator: 'CmdOrCtrl+S' },
          { role: 'separator' },
          { role: 'quit' },
        ],
      },
    },
    {
      label: 'Edit',
      submenu: {
        items: [
          { role: 'undo' },
          { role: 'redo' },
          { role: 'separator' },
          { role: 'cut' },
          { role: 'copy' },
          { role: 'paste' },
          { role: 'selectall' },
        ],
      },
    },
  ],
});

const win = app.createBrowserWindow({ title: 'My App' });
win.createWebview({ url: 'https://example.com' });

app.run();
```

`setMenu()` is **additive** — calling it a second time replaces the current menu entirely. Pass `null` to remove the menu bar.

```js theme={null}
// Remove the menu bar
app.setMenu(null);
```

***

## Handling menu clicks

Listen for the `custom-menu-click` application event to respond to clicks on any menu item that has an `id`. The payload contains a `customMenuEvent` object with the clicked item's identifier and the ID of the window it originated from.

```js theme={null}
app.on('custom-menu-click', ({ customMenuEvent }) => {
  console.log('Menu item clicked:', customMenuEvent.id);
  console.log('From window:',       customMenuEvent.windowId);

  switch (customMenuEvent.id) {
    case 'file-new':
      createNewDocument();
      break;
    case 'file-open': {
      const paths = win.openFileDialog({ title: 'Open file' });
      if (paths.length) openFile(paths[0]);
      break;
    }
    case 'file-save':
      saveDocument();
      break;
  }
});
```

| Field      | Type     | Description                                                   |
| ---------- | -------- | ------------------------------------------------------------- |
| `id`       | `string` | The `id` set on the `MenuItemOptions` that was clicked.       |
| `windowId` | `number` | The numeric ID of the window the click event originated from. |

Items that use a `role` (e.g. `copy`, `quit`) are handled natively and do **not** emit `custom-menu-click`.

***

## Updating menus at runtime

Replace the active menu at any time by calling `app.setMenu()` again with a new `MenuOptions` object. The menu bar updates immediately on Windows and macOS.

```js theme={null}
// Disable the Save item while a save is in progress
function setSaveEnabled(enabled) {
  app.setMenu({
    items: [
      {
        label: 'File',
        submenu: {
          items: [
            { id: 'file-save', label: 'Save', accelerator: 'CmdOrCtrl+S', enabled },
            { role: 'quit' },
          ],
        },
      },
    ],
  });
}
```

To remove the menu bar entirely, pass `null`:

```js theme={null}
app.setMenu(null);
```

***

## Per-window menus

Call `win.setMenu(MenuOptions)` to attach a menu that overrides the global menu for that specific window. All other windows continue to use the global menu.

```js theme={null}
const editorWin = app.createBrowserWindow({ title: 'Editor' });

editorWin.setMenu({
  items: [
    {
      label: 'Format',
      submenu: {
        items: [
          { id: 'fmt-bold',   label: 'Bold',   accelerator: 'CmdOrCtrl+B' },
          { id: 'fmt-italic', label: 'Italic', accelerator: 'CmdOrCtrl+I' },
        ],
      },
    },
  ],
});
```

Per-window menu clicks also fire `custom-menu-click` on `app`. Use `customMenuEvent.windowId` to tell which window originated the click.

***

## MenuItemOptions

Every entry in a `submenu.items` array is a `MenuItemOptions` object.

```ts theme={null}
interface MenuItemOptions {
  id?:          string;
  label?:       string;
  enabled?:     boolean;
  accelerator?: string;
  role?:        string;
  submenu?:     MenuOptions;
}
```

| Field         | Type          | Default | Description                                                                                                     |
| ------------- | ------------- | ------- | --------------------------------------------------------------------------------------------------------------- |
| `id`          | `string`      | —       | Unique identifier emitted in `custom-menu-click` events. Required for custom items you want to handle in code.  |
| `label`       | `string`      | —       | Display text shown in the menu. Not required when using `role: 'separator'`.                                    |
| `enabled`     | `boolean`     | `true`  | When `false` the item is shown but grayed out and unclickable.                                                  |
| `accelerator` | `string`      | —       | Keyboard shortcut string (see [Keyboard accelerators](#keyboard-accelerators)). Displayed next to the label.    |
| `role`        | `string`      | —       | Maps to a predefined native action (see [Predefined roles](#predefined-roles)). Overrides `label` and `id`.     |
| `submenu`     | `MenuOptions` | —       | Nested `MenuOptions` object. When present the item becomes a submenu parent and `id`/`accelerator` are ignored. |

***

## Predefined roles

Roles map menu items to native platform actions and are automatically localised into the system language. You do not need to supply `label`, `id`, or `accelerator` for role items.

| Role                                     | Action                         | Platform availability |
| ---------------------------------------- | ------------------------------ | --------------------- |
| `copy`                                   | Copy selection to clipboard    | All                   |
| `paste`                                  | Paste from clipboard           | All                   |
| `cut`                                    | Cut selection to clipboard     | All                   |
| `undo`                                   | Undo last action               | All                   |
| `redo`                                   | Redo last undone action        | All                   |
| `selectall` / `select-all`               | Select all content             | All                   |
| `separator` / `-`                        | Horizontal separator line      | All                   |
| `minimize`                               | Minimise the window            | All                   |
| `maximize`                               | Maximise the window            | All                   |
| `fullscreen`                             | Toggle fullscreen mode         | All                   |
| `close` / `closewindow` / `close-window` | Close the current window       | All                   |
| `quit`                                   | Quit the application           | All                   |
| `about`                                  | Show the native About dialog   | All                   |
| `hide`                                   | Hide the application           | macOS only            |
| `hideothers` / `hide-others`             | Hide all other applications    | macOS only            |
| `showall` / `show-all`                   | Show all applications          | macOS only            |
| `services`                               | Insert the Services submenu    | macOS only            |
| `bringalltofront` / `bring-all-to-front` | Bring all windows to the front | macOS only            |

***

## Keyboard accelerators

An accelerator string defines a keyboard shortcut displayed next to the menu label. The format is a `+`-separated list of modifiers followed by a key name.

| Token       | Meaning                                     |
| ----------- | ------------------------------------------- |
| `CmdOrCtrl` | `Cmd` on macOS, `Ctrl` on Windows and Linux |
| `Ctrl`      | `Ctrl` on all platforms                     |
| `Alt`       | `Alt` / `Option`                            |
| `Shift`     | `Shift`                                     |
| `Super`     | `Win` key on Windows, `Super` on Linux      |

**Examples**

| Accelerator string  | Result on macOS | Result on Windows / Linux |
| ------------------- | --------------- | ------------------------- |
| `CmdOrCtrl+S`       | `⌘S`            | `Ctrl+S`                  |
| `CmdOrCtrl+Shift+S` | `⇧⌘S`           | `Ctrl+Shift+S`            |
| `Alt+F4`            | `⌥F4`           | `Alt+F4`                  |
| `Shift+CmdOrCtrl+Z` | `⇧⌘Z`           | `Ctrl+Shift+Z`            |
| `F5`                | `F5`            | `F5`                      |

***

## Nested submenus

Set the `submenu` field on any menu item to create a hierarchical menu structure. Nesting depth is limited only by the platform's native rendering.

```js theme={null}
app.setMenu({
  items: [
    {
      label: 'View',
      submenu: {
        items: [
          { id: 'reload',   label: 'Reload',          accelerator: 'CmdOrCtrl+R' },
          { id: 'devtools', label: 'Developer Tools',  accelerator: 'F12' },
          { role: 'separator' },
          {
            label: 'Zoom',          // parent item — no id or accelerator
            submenu: {
              items: [
                { id: 'zoom-in',    label: 'Zoom In',    accelerator: 'CmdOrCtrl+Plus' },
                { id: 'zoom-out',   label: 'Zoom Out',   accelerator: 'CmdOrCtrl+-' },
                { id: 'zoom-reset', label: 'Actual Size', accelerator: 'CmdOrCtrl+0' },
              ],
            },
          },
        ],
      },
    },
  ],
});
```

When an item has a `submenu`, the `id` and `accelerator` fields on that parent item are ignored — only `label` and `enabled` apply.

***

## Platform differences

| Platform    | Behaviour                                                                                                                                                                                             |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Windows** | Each window has its own menu bar attached inside the title-bar area.                                                                                                                                  |
| **macOS**   | A single app-level menu bar is displayed at the top of the screen; it is shared globally.                                                                                                             |
| **Linux**   | Per-window GTK menu bar. Requires WebKitGTK 4.1 (`libwebkit2gtk-4.1-dev`). `custom-menu-click` events fire normally. Display depends on the desktop environment — test on your target configurations. |
