Electron - System Tray
Electron - System Tray
Section titled “Electron - System Tray”A system tray icon (or Notification Area icon on Windows, Menu Bar icon on macOS) provides a way for applications to offer persistent access and status indication, even when the main application window is closed or hidden. Electron allows you to create and manage these icons and their associated context menus.
Important: System tray icons and their menus should be managed from the main process, not the renderer process. Attempting to manage them from the renderer using the (now deprecated and unsafe) remote module is discouraged.
Let’s create a simple system tray icon with a context menu. You’ll need an icon image (e.g., a 16x16 or 32x32 PNG file). For this example, assume you have an icon.png in an assets subfolder relative to your main.js file.
Modify your main.js file:
const { app, BrowserWindow, Tray, Menu, nativeImage } = require('electron');const path = require('path');
let mainWindow;let tray = null; // Keep a reference to the Tray object
function createWindow() { mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { // Preload script can be added if renderer needs to communicate about tray actions // preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false, } });
mainWindow.loadFile(path.join(__dirname, 'index.html'));
mainWindow.on('closed', () => { mainWindow = null; });}
function createTray() { // It's recommended to use nativeImage.createFromPath for icons. // Ensure the path is correct and the icon exists. // For macOS, template images are often preferred (black and white, adapting to theme). const iconPath = path.join(__dirname, 'assets', 'icon.png'); // Adjust path as needed const icon = nativeImage.createFromPath(iconPath);
if (icon.isEmpty()) { console.error('Failed to load tray icon. Is the path correct and image valid?'); // Fallback or error handling tray = new Tray(nativeImage.createEmpty()); // Creates a blank space, better than crashing } else { tray = new Tray(icon); }
const contextMenu = Menu.buildFromTemplate([ { label: 'Show App', click: () => { if (mainWindow) { mainWindow.show(); } else { createWindow(); } } }, { label: 'Item 1', click: () => { console.log('Clicked Item 1'); // If you need to notify the renderer, use IPC: // if (mainWindow) mainWindow.webContents.send('tray-action', 'item1-clicked'); } }, { label: 'Quit', click: () => { app.quit(); // Quits the application } } ]);
tray.setToolTip('My Electron App'); tray.setContextMenu(contextMenu);
// Optional: Handle left-click on tray icon (e.g., toggle window visibility) tray.on('click', () => { if (mainWindow) { if (mainWindow.isVisible()) { mainWindow.hide(); } else { mainWindow.show(); } } });}
app.whenReady().then(() => { createWindow(); createTray();
app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } });});
// Prevent app from quitting when all windows are closed if tray icon is present (common behavior)// This depends on your desired UX.app.on('window-all-closed', () => { // On macOS, it's common for applications to stay active until the user quits explicitly (Cmd+Q) // On Windows/Linux, if you want the app to close, remove this or app.quit() if (process.platform === 'darwin') { // Do nothing, app stays open with tray icon } else { // If you want the app to quit, then app.quit(). // If you want it to stay alive (tray only), then do nothing here. // For this example, let's assume we want it to stay active if tray is present. }});
// Ensure tray is destroyed when app quits to prevent zombie icons on some platformsapp.on('before-quit', () => { if (tray) { tray.destroy(); }});Create a simple index.html file. Its content isn’t critical for this tray example, as all logic is in main.js:
<!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>Electron Tray App</title></head><body> <h1>My Electron Application</h1> <p>Check your system tray for the application icon!</p></body></html>Make sure you have an icon.png (or your chosen icon file) in an assets folder (e.g., your-app-root/assets/icon.png).
Run the application using npm start (assuming your package.json is configured).
You should see your application window and an icon in your system tray. Right-clicking the tray icon will show the context menu you defined. Clicking ‘Show App’ will focus or recreate the window, and ‘Quit’ will exit the application.
Best Practices and Considerations:
Section titled “Best Practices and Considerations:”- Icon Paths: Use
path.join(__dirname, ...)for reliable paths to your assets.nativeImage.createFromPath()is the recommended way to load icons. - Icon Size and Type: Tray icons are small. Typically 16x16 or 32x32 pixels. PNG is a common format. macOS supports ‘template images’ which adapt to the system theme (usually black and white).
- Main Process Logic: All
TrayandMenucreation and management must happen in the main process. - IPC for Renderer Interaction: If a tray menu action needs to affect a renderer process (e.g., open a specific view), use IPC (
mainWindow.webContents.send()) to communicate from main to renderer. - Lifecycle Management: Keep a reference to your
Trayobject (tray = new Tray(...)) to prevent it from being garbage collected prematurely. Explicitly destroy the tray icon (tray.destroy()) in thebefore-quitevent of the app to clean up resources, especially on Linux. - Platform Differences: Behavior and appearance of tray icons can vary slightly across macOS, Windows, and Linux. Test on all target platforms.
For more advanced tray features, such as updating the icon dynamically or handling different click events, refer to the Electron Tray documentation.