Tray-Icons sind kleine Icons in der System-Bar (macOS-Menüleiste rechts oben, Windows-Notification-Area unten rechts, Linux je nach Desktop) — typisch für Hintergrund-Apps wie Dropbox, Slack-Mini-Mode oder Custom-Tools. Hier wie sie funktionieren und welche Plattform-Fallen drinstecken.
Minimal-Beispiel
import { app, Tray, Menu, nativeImage } from 'electron';
import path from 'node:path';
let tray;
app.whenReady().then(() => {
const icon = nativeImage.createFromPath(
path.join(__dirname, 'assets/tray-icon.png')
);
tray = new Tray(icon);
tray.setToolTip('My App');
const menu = Menu.buildFromTemplate([
{ label: 'Öffnen', click: () => mainWindow.show() },
{ type: 'separator' },
{ label: 'Beenden', click: () => app.quit() }
]);
tray.setContextMenu(menu);
});tray muss als globale Variable gehalten werden — sonst kann der Garbage Collector sie räumen, und das Icon verschwindet.
Icon-Formate je Plattform
| Plattform | Empfohlen | Größe |
|---|---|---|
| macOS | PNG mit Template-Suffix für Auto-Theming | 16×16 oder 32×32 (HiDPI: 2x) |
| Windows | PNG, ICO | 16×16 (HiDPI: 32×32) |
| Linux | PNG | 22×22 (Distro-abhängig) |
macOS — Template Images
macOS-Tray-Icons sollten monochrom sein und automatisch dem Theme folgen (hell/dunkel). Trick: Datei-Suffix Template:
assets/
├── trayIconTemplate.png
├── trayIconTemplate@2x.png ← HiDPI
└── trayIconTemplate@3x.png ← Retinaconst icon = nativeImage.createFromPath('assets/trayIconTemplate.png');
tray = new Tray(icon);macOS erkennt das Template und färbt das Icon automatisch — schwarz auf hellem Theme, weiß auf dunklem.
Click-Handler
tray.on('click', () => {
// Linksklick: typisch Hauptfenster zeigen/verstecken
if (mainWindow.isVisible()) mainWindow.hide();
else mainWindow.show();
});
tray.on('right-click', () => {
// Wenn kein ContextMenu via setContextMenu gesetzt:
tray.popUpContextMenu(customMenu);
});
tray.on('double-click', () => {
mainWindow.show();
});Auf macOS löst Linksklick standardmäßig das ContextMenu aus (wenn gesetzt) — das click-Event feuert nur, wenn KEIN ContextMenu definiert ist.
Auf Windows verhält sich das anders: click feuert immer, das ContextMenu kommt nur per Rechtsklick.
Title und Tooltip
// Tooltip beim Hover
tray.setToolTip('My App — 3 ungelesen');
// Title (NUR macOS): Text neben dem Icon
tray.setTitle('3'); // z. B. ungelesene-Counter
tray.setTitle('—', { fontType: 'monospacedDigit' });setTitle funktioniert nur auf macOS — für Windows-Counter besser App-Badge oder direkt im Icon ansetzen (alternative Icons mit Zahl rendern).
Tray + Hauptfenster
Klassisches Pattern für Hintergrund-Apps: Hauptfenster auf X versteckt, statt geschlossen.
let mainWindow;
let isQuitting = false;
app.whenReady().then(() => {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
show: false
});
// X-Klick: NICHT schließen, nur verstecken
mainWindow.on('close', (event) => {
if (!isQuitting) {
event.preventDefault();
mainWindow.hide();
}
});
tray = new Tray(icon);
tray.setContextMenu(Menu.buildFromTemplate([
{ label: 'Zeigen', click: () => mainWindow.show() },
{ label: 'Beenden', click: () => {
isQuitting = true;
app.quit();
}}
]));
});isQuitting-Flag verhindert die Auto-Hide-Logik beim echten Quit. Ohne das Flag könnte die App nicht mehr beendet werden.
macOS: Dock verstecken bei Tray-Apps
Reine Tray-Apps haben oft kein Dock-Icon — das ist auf macOS möglich:
if (process.platform === 'darwin') {
app.dock.hide();
}Dann läuft die App komplett im Hintergrund, sichtbar nur als Menüleisten-Icon. Wer manchmal das Hauptfenster zeigt: vorm Anzeigen app.dock.show() rufen, beim Verstecken wieder hide().
Besonderheiten
tray-Variable global halten — sonst Garbage-Collected.
Klassischer Anfänger-Bug: const tray = new Tray(...) in einem Funktions-Scope. Sobald die Funktion zurückkehrt, ist tray ein Kandidat für GC und das Icon verschwindet. Pflicht: globale Variable im Module-Scope.
Template-Suffix für macOS Auto-Theming.
Datei-Name iconTemplate.png (oder iconTemplate@2x.png für HiDPI) — macOS färbt es automatisch passend zum Theme. Ohne Suffix: hartcodiertes Icon, sieht im Dark-Mode oft falsch aus.
Linksklick-Verhalten ist Plattform-unterschiedlich.
macOS: Linksklick öffnet ContextMenu (wenn gesetzt) — kein separates click-Event. Windows: click feuert immer, ContextMenu kommt per Rechtsklick. Wer „Hauptfenster togglen bei Klick" will, muss das je Plattform handhaben.
setTitle nur auf macOS.
Windows-Tray-Icon zeigt keinen Title-Text neben dem Icon. Wer Counter braucht: dynamisches Icon rendern (mit Zahl) und via setImage swappen. Aufwändiger, aber dafür sichtbar auf jeder Plattform.
Hide statt Close beim X-Button.
Pattern für Hintergrund-Apps: mainWindow.on('close', e => { e.preventDefault(); mainWindow.hide(); }). Mit isQuitting-Flag kombinieren, sonst lässt sich die App nicht mehr beenden.
Linux-Tray-Verhalten ist sehr Distro-spezifisch.
GNOME hat seit Jahren keinen Standard-System-Tray mehr (Workaround: AppIndicator-Extension). KDE und XFCE funktionieren gut. Ubuntu hat Eigen-Lösung. Bei Linux-Support: testen, testen, testen.
Weiterführende Ressourcen
Externe Quellen
- Tray
- nativeImage — Bild-Erzeugung mit Template-Support
- Menubar Apps Tutorial