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

JavaScript
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

PlattformEmpfohlenGröße
macOSPNG mit Template-Suffix für Auto-Theming16×16 oder 32×32 (HiDPI: 2x)
WindowsPNG, ICO16×16 (HiDPI: 32×32)
LinuxPNG22×22 (Distro-abhängig)

macOS — Template Images

macOS-Tray-Icons sollten monochrom sein und automatisch dem Theme folgen (hell/dunkel). Trick: Datei-Suffix Template:

Bash
assets/
├── trayIconTemplate.png
├── trayIconTemplate@2x.png       ← HiDPI
└── trayIconTemplate@3x.png       ← Retina
JavaScript
const 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

JavaScript
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

JavaScript
// 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.

JavaScript
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:

JavaScript
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

/ Weiter

Zurück zu Main-Prozess

Zur Übersicht