Native Menüs sind eines der Erkennungsmerkmale einer richtigen Desktop-App. Electrons Menu baut sie aus deklarativen Templates — mit vorgefertigten Roles für Standard-Aktionen wie Copy/Paste/Quit, plattform-konformen Accelerators und macOS-spezifischen Konventionen.

Application Menu — die Menüleiste

JavaScript
import { app, Menu } from 'electron';

const isMac = process.platform === 'darwin';

const template = [
    // macOS: erste Position ist immer der App-Name
    ...(isMac ? [{
        label: app.name,
        submenu: [
            { role: 'about' },
            { type: 'separator' },
            { role: 'services' },
            { type: 'separator' },
            { role: 'hide' },
            { role: 'hideOthers' },
            { role: 'unhide' },
            { type: 'separator' },
            { role: 'quit' }
        ]
    }] : []),
    {
        label: 'Datei',
        submenu: [
            { label: 'Neu', accelerator: 'CmdOrCtrl+N', click: () => createDoc() },
            { label: 'Öffnen…', accelerator: 'CmdOrCtrl+O', click: () => openDoc() },
            { type: 'separator' },
            isMac ? { role: 'close' } : { role: 'quit' }
        ]
    },
    {
        label: 'Bearbeiten',
        submenu: [
            { role: 'undo' },
            { role: 'redo' },
            { type: 'separator' },
            { role: 'cut' },
            { role: 'copy' },
            { role: 'paste' },
            { role: 'selectAll' }
        ]
    }
];

const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);

Roles — vorgefertigte Aktionen

RoleAktion
undo, redoStandard-Editing
cut, copy, paste, selectAllClipboard-Operationen
quitApp beenden
reload, forceReloadRenderer neu laden
toggleDevToolsDevTools auf/zu
togglefullscreenVollbild-Toggle
minimize, closeFenster-Aktionen
aboutmacOS-Standard-About-Dialog
services, hide, hideOthers, unhidemacOS-spezifisch

Vorteil von Roles: sie machen automatisch das Richtige auf jeder Plattform — Copy/Paste auf macOS via Cmd+C, auf Windows via Ctrl+C, ohne dass du das selbst handhaben musst.

Accelerators — Tastenkürzel

JavaScript
{ label: 'Speichern',     accelerator: 'CmdOrCtrl+S' }
{ label: 'Speichern als', accelerator: 'CmdOrCtrl+Shift+S' }
{ label: 'Suchen',        accelerator: 'CmdOrCtrl+F' }

// Plattform-spezifisch
{ label: 'Pref',
  accelerator: process.platform === 'darwin' ? 'Cmd+,' : 'Ctrl+P' }

// Funktionstasten
{ label: 'F1', accelerator: 'F1' }

CmdOrCtrl ist der wichtigste Modifier — wird auf macOS zu Cmd, auf Windows/Linux zu Ctrl. Weitere: Alt, Option (=Alt), AltGr, Shift, Super (Windows-Taste).

Context Menu — Rechtsklick

JavaScript
import { Menu } from 'electron';

const contextMenu = Menu.buildFromTemplate([
    { label: 'Kopieren', role: 'copy' },
    { label: 'Einfügen', role: 'paste' },
    { type: 'separator' },
    { label: 'Suchen', click: () => searchSelection() }
]);

// Trigger via IPC vom Renderer
ipcMain.on('show-context-menu', (event) => {
    const win = BrowserWindow.fromWebContents(event.sender);
    contextMenu.popup({ window: win });
});
JavaScript preload.js
contextBridge.exposeInMainWorld('api', {
    showContextMenu: () => ipcRenderer.send('show-context-menu')
});
JavaScript renderer.js
document.addEventListener('contextmenu', (e) => {
    e.preventDefault();
    window.api.showContextMenu();
});

preventDefault blockt das Browser-eigene Context-Menü, damit deins erscheint.

Dynamische Menüs

Menüs sind nach setApplicationMenu „eingefroren" — Items lassen sich nur über Replacement aktualisieren:

JavaScript
function rebuildMenu(state) {
    const template = [
        {
            label: 'Datei',
            submenu: [
                {
                    label: 'Speichern',
                    enabled: state.isDirty,        // dynamisch
                    accelerator: 'CmdOrCtrl+S',
                    click: () => save()
                },
                {
                    label: 'Schließen',
                    accelerator: 'CmdOrCtrl+W',
                    click: () => closeDoc()
                }
            ]
        }
    ];
    Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}

// bei State-Änderung
store.on('change', rebuildMenu);

Pragmatisch: bei jeder relevanten State-Änderung neu bauen. Performance-mäßig kein Problem, weil Menüs sehr klein sind.

Submenüs und Checkboxen

JavaScript
{
    label: 'Theme',
    submenu: [
        {
            label: 'Hell',
            type: 'radio',
            checked: theme === 'light',
            click: () => setTheme('light')
        },
        {
            label: 'Dunkel',
            type: 'radio',
            checked: theme === 'dark',
            click: () => setTheme('dark')
        },
        { type: 'separator' },
        {
            label: 'Toolbar zeigen',
            type: 'checkbox',
            checked: state.showToolbar,
            click: (item) => toggleToolbar(item.checked)
        }
    ]
}

type: 'radio' und type: 'checkbox' machen Standard-UI-Patterns ohne extra Logik.

Interessantes

Roles statt Custom-Click — wo möglich.

role: 'copy' ist auf jeder Plattform „das richtige Copy". Wer manuell clipboard.writeText ruft, hat plattform-spezifische Edge-Cases (Selection in Input-Feldern, Format-Erhalt etc.) selber zu handhaben. Roles erledigen das.

CmdOrCtrl ist der Cross-Platform-Modifier.

Niemals Cmd+S hardcoden — auf Windows wäre das tot. CmdOrCtrl+S macht's automatisch richtig. Gleiches für Cmd+Shift+S etc.

macOS hat Konventionen — z. B. App-Name als erstes Menü.

Auf macOS gehört der App-Name immer als erste Position. Daraus „Beenden" via role: 'quit' ist Pflicht. Die Edit-Submenü-Struktur ist auch konventional. Beim Cross-Platform-Build die macOS-Sektion mit isMac && einklemmen.

setApplicationMenu(null) versteckt das Menü.

Nützlich für Kiosk-Apps oder minimale Tools. Auf macOS bleibt das App-Menü trotzdem (System-Anforderung) — auf Windows/Linux ist die Menü-Bar weg.

Context Menu MUSS vom Renderer angefragt werden.

Im Renderer triggern, im Main per IPC popup() ausführen. Kein direkter Zugriff aus dem Renderer auf Menu (Sicherheits-Trennung). Pattern: Renderer schickt IPC, Main zeigt das Menü, Item-Click wieder via IPC zurück (oder direkt im Main-Click-Handler).

Dynamische Menüs durch komplette Neuerstellung.

Es gibt kein „update item X" — der ganze Menu wird neu gebaut. Bei häufigen Updates trotzdem performant; aber nicht im sub-Frame-Tempo, sondern bei tatsächlichen State-Changes.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Main-Prozess

Zur Übersicht