> ## 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.

# Native Application Menus for Your WebviewJS Desktop App

> Create native menu bars with keyboard accelerators, predefined roles, and per-window menus. Handle menu clicks with custom-menu-click events.

WebviewJS exposes a cross-platform native menu system powered by [muda](https://github.com/tauri-apps/muda). You define a tree of menu items — labels, roles, accelerators, and submenus — and the OS renders it natively. On macOS the menu bar lives at the top of the screen and belongs to the application. On Windows each window has its own menu bar embedded in the title bar. On Linux a per-window GTK menu bar is attached through Tao's GTK integration; `custom-menu-click` events fire on all three platforms. See [Platform differences](#platform-differences) for details.

***

## Setting a global menu

Call `app.setMenu(options)` before or after creating windows. The menu applies to all windows that do not have a per-window override.

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

const app = new Application();

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

const win = app.createBrowserWindow({ title: 'My App' });
const webview = win.createWebview({ url: 'app://localhost/index.html' });

app.run();
```

***

## Handling menu clicks

Listen for `custom-menu-click` on the `Application` instance. The event payload contains a `customMenuEvent` object with the `id` you assigned to the menu item and the `windowId` of the window that was frontmost when the item was activated.

```js theme={null}
app.on('custom-menu-click', ({ customMenuEvent }) => {
  switch (customMenuEvent.id) {
    case 'new':
      createNewWindow();
      break;
    case 'open':
      openFilePicker();
      break;
    case 'save':
      saveCurrentDocument();
      break;
    default:
      console.log('Unhandled menu item:', customMenuEvent.id);
  }
});
```

Only items with an `id` property emit this event. Role-based items such as `copy` and `paste` are handled natively by the OS and do not emit `custom-menu-click`.

***

## Updating menus at runtime

Replace the entire menu tree at any time by calling `app.setMenu()` again with a new item list. Pass `null` to remove the menu bar entirely.

```js theme={null}
// Replace the whole menu
app.setMenu({ items: updatedItems });

// Remove the menu bar entirely
app.setMenu(null);
```

***

## Per-window menus

Call `win.setMenu()` to assign a menu that applies only to a specific window. This overrides the global menu for that window. Clicks still emit `custom-menu-click` on the application.

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

win.setMenu({
  items: [
    {
      label: 'Editor',
      submenu: {
        items: [
          { id: 'editor-prefs', label: 'Preferences' },
        ],
      },
    },
  ],
});
```

***

## Keyboard accelerators

Specify accelerators with the `accelerator` property on any custom menu item. Use the following cross-platform modifier tokens:

| Accelerator string  | Result                                      |
| ------------------- | ------------------------------------------- |
| `CmdOrCtrl+S`       | `Cmd+S` on macOS, `Ctrl+S` on Windows/Linux |
| `Alt+F4`            | `Alt+F4` (literal)                          |
| `Shift+CmdOrCtrl+Z` | Redo shortcut, cross-platform               |
| `F5`                | Function key `F5`                           |
| `F11`               | Function key `F11`                          |

```js theme={null}
{ id: 'save', label: 'Save', accelerator: 'CmdOrCtrl+S' }
{ id: 'redo', label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z' }
{ id: 'reload', label: 'Reload', accelerator: 'F5' }
```

***

## Predefined roles

Use `role` instead of `id` + `label` to get OS-native behavior without writing click handlers. Role items are rendered and wired by the platform.

| Role                                     | Description                            |
| ---------------------------------------- | -------------------------------------- |
| `separator` / `-`                        | Horizontal divider line                |
| `copy`                                   | Copy selection to clipboard            |
| `paste`                                  | Paste from clipboard                   |
| `cut`                                    | Cut selection to clipboard             |
| `undo`                                   | Undo last action                       |
| `redo`                                   | Redo last undone action                |
| `selectall` / `select-all`               | Select all text                        |
| `minimize`                               | Minimize the window                    |
| `maximize`                               | Maximize the window                    |
| `fullscreen`                             | Toggle full-screen mode                |
| `close` / `closewindow` / `close-window` | Close the current window               |
| `quit`                                   | Quit the application                   |
| `about`                                  | Show the platform About dialog (macOS) |
| `hide`                                   | Hide the application (macOS)           |
| `hideothers` / `hide-others`             | Hide all other applications (macOS)    |
| `showall` / `show-all`                   | Show all hidden applications (macOS)   |
| `services`                               | Services submenu (macOS)               |
| `bringalltofront` / `bring-all-to-front` | Bring all windows to front (macOS)     |

***

## Nested submenus

Nest a `submenu` inside any top-level or secondary menu item to create hierarchical menus.

```js theme={null}
{
  label: 'View',
  submenu: {
    items: [
      {
        label: 'Zoom',
        submenu: {
          items: [
            { id: 'zoom-in',    label: 'Zoom In',  accelerator: 'CmdOrCtrl+=' },
            { id: 'zoom-out',   label: 'Zoom Out', accelerator: 'CmdOrCtrl+-' },
            { id: 'zoom-reset', label: 'Reset',    accelerator: 'CmdOrCtrl+0' },
          ],
        },
      },
      { role: 'fullscreen' },
    ],
  },
}
```

***

## Platform differences

| Feature                    | Windows                          | macOS                    | Linux                   |
| -------------------------- | -------------------------------- | ------------------------ | ----------------------- |
| Menu bar location          | Per-window, inside the title bar | App-level, top of screen | Per-window GTK menu bar |
| Predefined roles           | Most roles supported             | All roles supported      | Most roles supported    |
| Keyboard accelerators      | Yes                              | Yes                      | Yes                     |
| `custom-menu-click` events | Yes                              | Yes                      | Yes                     |

<Note>
  On Linux, WebviewJS uses Muda's GTK integration to attach a per-window menu
  bar. Both `app.setMenu()` and `win.setMenu()` work, and `custom-menu-click`
  events fire as expected. The visual appearance is determined by the GTK theme
  of the desktop environment.
</Note>

***

## Related pages

* [Multiple Windows](/guides/multiple-windows) — per-window menus and window lifecycle
* [Application API reference](/api/application) — `setMenu`, `on('custom-menu-click')`, and application events
