menubar: Building Electron System Tray Applications with One Function Call
➖ high level way to create menubar desktop applications with electron
At a glance
- What is it?
- menubar is an npm package that wraps Electron's Tray and BrowserWindow APIs behind a single menubar() function, handling window positioning, show/hide on click, and cross-platform tray icon management. It works on macOS, Windows, and most Linux distributions and carries one runtime dependency.
- Who is it for?
- menubar is the right fit for engineers who want to ship an Electron tray application without hand-wiring window positioning, blur-to-hide logic, and tray icon handling from scratch. It is not suited to production applications that need fine-grained control over window lifecycle because the abstraction hides several Electron events behind its own event model.
- Can I use it commercially?
- Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 61 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What menubar Solves for Electron Tray Application Authors
Building a system tray application with Electron from scratch requires assembling several moving parts: creating a Tray instance, creating a BrowserWindow, attaching a click handler to the tray icon, positioning the window near the tray icon on click, and hiding the window when the user clicks away. Getting window positioning right across macOS, Windows, and Linux is not trivial because each platform places the tray in a different screen region.
menubar packages all of that boilerplate. Point it at an index.html file and call menubar() with no arguments; the library creates the tray icon, creates the browser window, positions it relative to the tray, and shows or hides it on click. The README states the design goal directly: all you have to do is point it at your index.html and menubar will handle the rest.
The package ships at 3.6kB minified and gzipped according to the README. It has one runtime dependency (electron-positioner) and declares Electron itself as a peer dependency. This keeps the package footprint minimal while leaving the Electron version choice to the application author.
How menubar Wraps Electron Internally
The return value of menubar() is an instance of the Menubar class. This class extends Electron's EventEmitter, so application code attaches listeners using the standard on() pattern. The class holds references to the underlying Electron objects as public properties: app, window, tray, and positioner. These are the real Electron instances, so any Electron API can be called on them directly when the menubar abstraction does not expose what is needed.
Window positioning is delegated to electron-positioner, the single runtime dependency. The library calculates where to place the BrowserWindow so it appears adjacent to the tray icon. The default windowPosition option places the window at trayCenter on most platforms and trayBottomCenter on Windows, which matches the expected placement for each system.
The showWindow() and hideWindow() methods on the Menubar class do what their names say. The setOption() and getOption() methods allow changing configuration after the menubar instance is created, without reconstructing it. The README also lists the reference API documentation location in the docs/ folder of the repository for developers who need the full parameter list.
Installing menubar and Writing a Minimal App
Install the package from npm and create the entry point and HTML file:
yarn add menubarThe README gives a working example for myApp.js:
const { menubar } = require('menubar');
const mb = menubar();
mb.on('ready', () => {
console.log('app is ready');
// your app code here
});Run the app with Electron:
electron myApp.jsThe ready event fires when the tray icon has been created and initialized. The README notes that this differs from Electron's own app ready event, which fires earlier in the process lifecycle. Code that needs the tray to exist before executing should go inside the ready handler, not in the top-level module scope. The examples/ folder in the repository includes a hello-world example with a complete working structure.
Configuring Options: Window Size, Icon Path, and Always-on-Top
The menubar() function accepts an options object. The dir option (default: process.cwd()) sets the app source directory. The index option sets the URL to load in the BrowserWindow; this can be a local file:// path or a remote http:// URL, which makes it possible to point menubar at a dev server during development.
Window dimensions default to 400x400. The browserWindow option passes any Electron BrowserWindow constructor options through, so width, height, alwaysOnTop, and other properties can be set there. Setting alwaysOnTop to true prevents the window from hiding when the user clicks elsewhere.
The icon option specifies the path to the tray icon PNG. A 20x20 image is a good starting size. For retina display support, supply a 40x40 image named with the @2x suffix alongside the standard image; Electron will pick the retina version automatically.
The preloadWindow option (default false) creates the BrowserWindow before it is first shown. This trades higher idle memory use for faster display on the first click. The showOnAllWorkspaces option (default true on macOS) makes the window visible across all virtual desktops. The showDockIcon option (default false) controls whether the application appears in the macOS Dock.
The Event Model: From ready to focus-lost
The Menubar class emits a sequence of events around window creation and visibility. The ready event fires after the tray icon is initialized. The create-window event fires immediately before new BrowserWindow() is called, which is the right place to set up any pre-creation state. The before-load event fires after window creation but before loadURL, making it the correct location to call require('@electron/remote/main').enable(webContents) if the remote module is needed.
The show, after-show, hide, and after-hide events bracket each window visibility change. The after-close event fires after the BrowserWindow instance has been removed from the Menubar object. The focus-lost event fires only when alwaysOnTop is set to true and the user clicks away from the window: this event distinguishes an intentional click-away from the normal hide-on-blur behavior that triggers when alwaysOnTop is false.
This event granularity matters for analytics, state persistence, or animation triggers. However, it is more limited than working directly with Electron's full BrowserWindow event set: developers who need events like did-finish-load or will-navigate must attach listeners to the mb.window property directly.
Compatibility, Limitations, and When to Use the Tray API Directly
menubar 9.x is compatible with Electron 9 and above. The README shows a compatibility table: 8.x targeted Electron 8, 7.x targeted Electron 7, and versions 6.x and below are explicitly noted as not recommended for security reasons. The README lists versions 5 and below as Please, please don't use these old versions.
The package does not solve every tray application requirement. Context menus require calling Electron's tray.setContextMenu() directly on mb.tray rather than through any menubar API. Multiple window layouts are outside the scope of the abstraction; menubar manages one BrowserWindow per instance. Applications that need native menus, system notifications, or protocol handlers must reach through to the underlying Electron APIs.
Linux compatibility has platform-specific gaps. The WORKING_PLATFORMS.md file in the repository documents which distributions and desktop environments are tested. Not every Linux configuration where Electron runs will display a tray icon correctly, which is a limitation of the Linux tray icon ecosystem rather than menubar specifically.
For teams who need total control over window lifecycle or who are building a complex tray application with multiple windows and context menus, working directly with Electron's Tray and BrowserWindow APIs without the menubar wrapper avoids the abstraction overhead.
Electron Compatibility Table and BSD-2-Clause License
The package.json shows version 9.5.3 is the current release. The project is continuously type-checked against Electron versions 35 through 43 in CI, according to the compatibility table in the README. The repository is TypeScript throughout, with compiled output in lib/. The package.json devDependencies show Electron 43.x as the test target.
The license is BSD-2-Clause. This is a permissive license that allows use in proprietary products without requiring source disclosure. The only conditions are preserving the copyright notice and the license text.
The last push was on 2026-07-30. The package continues to receive updates tracking the Electron release cadence. Anyone evaluating menubar for a new project should check the compatibility table against their intended Electron version before committing to it, since Electron's own API surface changes across major versions.
Editorial conclusion
menubar is the right fit for engineers who want to ship an Electron tray application without hand-wiring window positioning, blur-to-hide logic, and tray icon handling from scratch. It is not suited to production applications that need fine-grained control over window lifecycle because the abstraction hides several Electron events behind its own event model. Before adopting it, check the compatibility table against your target Electron version: menubar 9.x tracks Electron 9 and above, but the table shows that older menubar releases are not safe to use.
Frequently asked questions
How do I create a macOS menubar app with Electron and menubar?
Install the package with yarn add menubar, create an index.html with your UI, and in a JS entry point call const mb = menubar() and listen for the ready event before adding app logic. Run the result with electron myApp.js. The library handles tray icon creation and window positioning automatically.
Which Electron events does menubar expose for show and hide actions?
The Menubar class emits show, after-show, hide, and after-hide events around each window visibility change. The focus-lost event fires only when alwaysOnTop is enabled and the user clicks away. These bracket the standard hide-on-blur behavior that menubar manages automatically.
Does menubar work on Linux?
The README states that menubar works on macOS, Windows, and most Linuxes, with details in the WORKING_PLATFORMS.md file in the repository. Not every Linux desktop environment supports system tray icons consistently, so checking that file for your specific distribution and desktop is worthwhile.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/max-mapper-menubar)