# How Lepton Implements System Tray Icons and Context Menus in Electron

> Discover how Lepton seamlessly implements system tray icons and context menus within Electron using Tray and Menu APIs. Explore state management techniques for efficient control.

- Repository: [CosmoX/Lepton](https://github.com/hackjutsu/lepton)
- Tags: internals
- Published: 2026-02-23

---

**Lepton implements system tray icons and context menus entirely within the Electron main process using the `Tray` and `Menu` APIs, with state management handled through an `operationType` variable and a `setTray()` helper function.**

Lepton is an open-source snippet manager built on the Electron framework. Its system tray functionality allows users to minimize the application to the tray rather than closing it entirely, keeping the app accessible without cluttering the taskbar. This implementation resides primarily in the main process entry point and relies on Electron's native modules to manage icons, tooltips, and context menus.

## Tray Creation and Icon Configuration

The tray initialization logic is encapsulated in the `setTray(app, mainWindow)` helper function defined in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js). This function is invoked at three specific points during the application lifecycle: line 150 (during initial setup), line 171 (when configuring window behavior), and line 388 (when handling minimize actions).

The tray icon is loaded from a static asset path using Node.js path utilities:

```javascript
const iconPath = path.join(__dirname, 'build/icon/icon.png')
miniWindow = new Tray(iconPath)

```

This code appears at lines 389-412 of [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js). The variable `miniWindow` holds the `Tray` instance throughout the application session, remaining active as long as the process runs.

## Building the Context Menu

Lepton constructs the tray context menu using Electron's template-based menu system. The menu template is defined directly within the `setTray` function at lines 393-402:

```javascript
const trayMenuTemplate = [
  { label: 'Open Window', click: () => { mainWindow.show() } },
  { label: 'Quit', click: () => { operationType = 2; if (process.platform !== 'darwin') app.quit(); mainWindow = null } }
]

```

The template contains two actions: restoring the main window and terminating the application. The construction and attachment occur at lines 413-420:

```javascript
const contextMenu = Menu.buildFromTemplate(trayMenuTemplate)
miniWindow.setContextMenu(contextMenu)

```

This approach creates a native context menu that integrates with the host operating system's tray interface.

## Handling User Interactions

Beyond the context menu, Lepton configures additional tray interactions to improve usability. The tooltip is set at line 421 using `miniWindow.setToolTip('Lepton')`, displaying the application name on hover.

Double-click functionality is implemented through an event listener at lines 421-425:

```javascript
miniWindow.on('double-click', () => { mainWindow.show() })

```

This allows users to restore the hidden window quickly without accessing the context menu, providing a familiar interaction pattern for system tray applications.

## Integrating with Window Lifecycle

The tray functionality coordinates closely with window lifecycle management through a global state variable called `operationType`. This variable distinguishes between three operational states:

- **0** – Normal operation with the window visible
- **1** – Window minimized to tray (hidden but process running)
- **2** – Application quit sequence initiated

The `mainWindow.on('close', …)` handler at lines 36-74 of [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js) implements this logic. When a user attempts to close the window while `operationType` is 0, a dialog presents three options: "Never Mind," "Minimize to tray," and "Quit." Selecting "Minimize to tray" triggers `setTray()`, hides the window via `mainWindow.hide()`, prevents the close event with `e.preventDefault()`, and sets `operationType` to 1.

This state management ensures the tray icon persists only when the user explicitly chooses to minimize rather than quit, and it prevents accidental application termination.

## Key Implementation Files

The system tray functionality spans several files within the repository:

- **[`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js)** – Core Electron entry point containing the `setTray()` function, window lifecycle handlers, and tray event listeners
- **[`app/utilities/menu/mainMenu.js`](https://github.com/hackjutsu/Lepton/blob/main/app/utilities/menu/mainMenu.js)** – Defines the main application menu template, which operates independently from the tray menu but uses the same `Menu` API
- **`build/icon/icon.png`** – The icon asset displayed in the system tray
- **[`package.json`](https://github.com/hackjutsu/Lepton/blob/main/package.json)** – Declares Electron as the runtime dependency enabling the `Tray` and `Menu` modules

## Summary

- Lepton creates tray icons via the `setTray()` helper in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js) using Electron's `Tray` class and an icon path resolved to `build/icon/icon.png`
- Context menus are built from JSON templates using `Menu.buildFromTemplate()` and attached via `setContextMenu()` on the tray instance
- The `operationType` state variable (0/1/2) coordinates window visibility, distinguishing between normal operation, minimize-to-tray, and quit states
- User interactions including double-click to restore and tooltip display are handled through native Electron event listeners on the `miniWindow` tray instance
- The close event handler at lines 36-74 of [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js) integrates tray creation with user confirmation dialogs to prevent accidental closures

## Frequently Asked Questions

### How does Lepton distinguish between minimizing to tray and quitting the application?

Lepton uses a global `operationType` variable to track application state. When the user closes the window, a dialog offers three choices (lines 36-74 of [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js)). Selecting "Minimize to tray" sets `operationType` to 1, prevents the window close event, and initializes the tray. Selecting "Quit" sets `operationType` to 2 and allows the application to terminate normally.

### Can the tray icon path be customized in Lepton?

According to the source code in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js) (lines 389-412), the tray icon path is hardcoded to `build/icon/icon.png` using `path.join(__dirname, 'build/icon/icon.png')`. Users would need to modify this path in the source and rebuild the application to use a custom icon.

### Is the tray functionality cross-platform compatible?

The implementation uses Electron's standard `Tray` API, which is supported on macOS, Windows, and Linux. However, the quit handler includes a platform-specific check: `if (process.platform !== 'darwin') app.quit()`, which accounts for differences in macOS application lifecycle expectations where apps typically remain active until explicitly quit from the menu.

### How does the tray menu differ from the main application menu?

The tray menu is defined inline within the `setTray()` function in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js) and contains only "Open Window" and "Quit" actions. The main application menu is defined separately in [`app/utilities/menu/mainMenu.js`](https://github.com/hackjutsu/Lepton/blob/main/app/utilities/menu/mainMenu.js) and provides comprehensive application navigation. Both use `Menu.buildFromTemplate()`, but the tray menu is optimized for quick window management while the main menu offers full application functionality.