Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/webviewjs/webview/llms.txt

Use this file to discover all available pages before exploring further.

WebviewJS exposes a cross-platform menu system built on tao’s native menu APIs. On macOS the menu bar appears at the top of the screen as part of the application; on Windows it is embedded in each window’s title-bar area. Menus are defined as plain JavaScript objects describing a tree of items — no native handles or platform SDKs required. Custom items carry a string id that is delivered to an application event listener when the user clicks them, while predefined role items (copy, paste, undo, etc.) are handled natively by the platform without any application code.

Setting the Global Application Menu

Call app.setMenu(options) with a MenuOptions object before or after creating windows. On macOS this controls the single app-level menu bar; on Windows it becomes the default menu for every window that doesn’t override it.
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' },
          { role: 'selectall' },
        ],
      },
    },
  ],
});

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

Both the top-level menu and every submenu use the same MenuOptions shape:
interface MenuOptions {
  items: MenuItemOptions[];
}

interface MenuItemOptions {
  id?:          string;         // Identifier delivered in custom-menu-click events
  label?:       string;         // Display text (required for custom items)
  enabled?:     boolean;        // Grayed-out when false (default: true)
  accelerator?: string;         // Keyboard shortcut, e.g. "CmdOrCtrl+S"
  submenu?:     MenuOptions;    // Nested menu
  role?:        string;         // Predefined platform behavior (see below)
}
A menu item should have either a role or a label+id pair. Items with a submenu act as flyout parents — they do not fire click events themselves.

Predefined Roles

Role items are handled entirely by the platform. You do not need an event listener for them.

Text editing

cut · copy · paste · selectall · undo · redo

Window & app

quit · minimize · zoom · fullscreen · separator
{ role: 'copy' }      // Edit > Copy
{ role: 'paste' }     // Edit > Paste
{ role: 'separator' } // Visual divider line
{ role: 'quit' }      // Quits the application

Handling Custom Menu Clicks

Items that carry an id field fire a custom-menu-click event on the Application instance. The event payload includes both the item id and the windowId of the window that was focused when the menu was invoked.
app.on('custom-menu-click', ({ customMenuEvent }) => {
  console.log(`Clicked "${customMenuEvent.id}" from window ${customMenuEvent.windowId}`);

  switch (customMenuEvent.id) {
    case 'new':
      createNewDocument();
      break;
    case 'open':
      openFilePicker();
      break;
    case 'save':
      saveCurrentDocument();
      break;
    case 'preferences':
      openPreferencesWindow();
      break;
    case 'quit':
      app.exit();
      break;
  }
});
On Linux, CustomMenuClick events are never fired and menu rendering is not supported. Code that handles menu events should work correctly when those events are absent.

Complete Menu Example

import { Application } from '@webviewjs/webview';

const app = new Application();

app.on('custom-menu-click', ({ customMenuEvent: e }) => {
  switch (e.id) {
    case 'new':        createNewDocument(); break;
    case 'open':       openFilePicker();    break;
    case 'save':       save();              break;
    case 'reload':     webview.reload();    break;
    case 'devtools':   webview.openDevtools(); break;
    case 'zoom-in':    zoom(+1);            break;
    case 'zoom-out':   zoom(-1);            break;
    case 'zoom-reset': zoom(0);             break;
    case 'quit':       app.exit();          break;
  }
});

app.setMenu({
  items: [
    {
      label: 'File',
      submenu: {
        items: [
          { id: 'new',     label: 'New',      accelerator: 'CmdOrCtrl+N' },
          { id: 'open',    label: 'Open…',    accelerator: 'CmdOrCtrl+O' },
          { role: 'separator' },
          { id: 'save',    label: 'Save',     accelerator: 'CmdOrCtrl+S' },
          { id: 'save-as', label: 'Save As…', accelerator: 'CmdOrCtrl+Shift+S' },
          { role: 'separator' },
          { id: 'quit',    label: 'Quit',     accelerator: 'CmdOrCtrl+Q' },
        ],
      },
    },
    {
      label: 'Edit',
      submenu: {
        items: [
          { role: 'cut' },
          { role: 'copy' },
          { role: 'paste' },
          { role: 'selectall' },
          { role: 'separator' },
          { id: 'preferences', label: 'Preferences…', accelerator: 'CmdOrCtrl+,' },
        ],
      },
    },
    {
      label: 'View',
      submenu: {
        items: [
          { id: 'reload',   label: 'Reload',          accelerator: 'CmdOrCtrl+R' },
          { id: 'devtools', label: 'Developer Tools',  accelerator: 'F12' },
          { role: 'separator' },
          {
            label: 'Zoom',
            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' },
              ],
            },
          },
        ],
      },
    },
    {
      label: 'Help',
      submenu: {
        items: [
          { id: 'about', label: 'About' },
          { id: 'docs',  label: 'Documentation', accelerator: 'F1' },
        ],
      },
    },
  ],
});

Nested Submenus

Nest submenu objects to any depth. The platform renders them as flyout panels.
{
  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 Zoom', accelerator: 'CmdOrCtrl+0' },
          ],
        },
      },
      { role: 'fullscreen' },
    ],
  },
}

Accelerator Syntax

Accelerators use a portable syntax that maps to platform-native shortcuts:
CmdOrCtrl+S       →  Cmd+S on macOS, Ctrl+S elsewhere
CmdOrCtrl+Shift+Z →  Redo shortcut
Alt+F4            →  Literal Alt+F4 (Windows)
F5, F11, F12      →  Function keys
CmdOrCtrl+,       →  Preferences (macOS convention)
CmdOrCtrl+Plus    →  Zoom in

Window-Specific Menus

Pass a menu option to createBrowserWindow, or call win.setMenu() after the window is created. A per-window menu overrides the global application menu for that window only. Click events still fire on Application as custom-menu-click.
const win = app.createBrowserWindow({
  title: 'Editor',
  menu: {
    items: [
      {
        label: 'Editor',
        submenu: {
          items: [
            { id: 'editor-prefs', label: 'Preferences' },
            { id: 'word-wrap',    label: 'Toggle Word Wrap', accelerator: 'Alt+Z' },
          ],
        },
      },
    ],
  },
});

Updating Menus at Runtime

Replace the entire menu tree by calling app.setMenu() again. Pass null to remove the menu completely.
// Add a "Close Project" item after a project is opened
function onProjectOpened() {
  app.setMenu({
    items: [
      ...baseMenuItems,
      {
        label: 'Project',
        submenu: {
          items: [
            { id: 'close-project', label: 'Close Project', accelerator: 'CmdOrCtrl+W' },
          ],
        },
      },
    ],
  });
}

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

Platform Differences

FeatureWindowsmacOSLinux
Menu bar locationInside each window’s title barTop of screen (app-wide)Not supported
Predefined rolesMost roles supportedAll roles supportedN/A
Keyboard acceleratorsN/A
custom-menu-click eventsNever fires
win.hasMenu()Always false
Linux does not render application menus and never fires custom-menu-click. If your application relies on menu actions for critical workflows, provide alternative UI (toolbar buttons, keyboard shortcuts via keydown events) for Linux users.

Build docs developers (and LLMs) love