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
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
| Role | Aktion |
|---|---|
undo, redo | Standard-Editing |
cut, copy, paste, selectAll | Clipboard-Operationen |
quit | App beenden |
reload, forceReload | Renderer neu laden |
toggleDevTools | DevTools auf/zu |
togglefullscreen | Vollbild-Toggle |
minimize, close | Fenster-Aktionen |
about | macOS-Standard-About-Dialog |
services, hide, hideOthers, unhide | macOS-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
{ 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
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 });
});contextBridge.exposeInMainWorld('api', {
showContextMenu: () => ipcRenderer.send('show-context-menu')
});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:
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
{
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.