menubar: Electron tray apps without the boilerplate
➖ high level way to create menubar desktop applications with electron
At a glance
- What is it?
- The menubar package wraps Electron's Tray, BrowserWindow and positioner plumbing into one call. It suits small tray apps on macOS, Windows and most Linux desktops, and it assumes you already have an Electron toolchain.
- Who is it for?
- Adopt menubar if you want a tray or menu bar window and already ship Electron: one call, one runtime dependency, and a documented event list for wiring window behaviour. Do not adopt it if you need a full application shell with auto-update, packaging or code signing, because none of its documented options or events covers that.
- 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 107 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 November 14, 2026, and from our analysis. They are not legal advice.
Star history
- max-mapper/menubar6,784 ★ · +2
| Repository | 2026-09-23 | 2026-11-14 |
|---|---|---|
| max-mapper/menubar | 6.8k | 6.8k |
2 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
What menubar removes from an Electron tray app
Electron gives you a Tray class, a BrowserWindow class and a separate positioner library, and leaves you to connect them: create the tray, create the window, hide the window on blur, position it under the tray icon, decide whether it appears on every workspace, and decide whether the dock icon shows. menubar packages that sequence behind a single factory function. The README describes the module as "boilerplate for setting up a menubar application using Electron" and says all you have to do is point it at your index.html.
The audience is narrow and specific. If you are building a status item that opens a small HTML panel, this is the intended case. If you are building a document editor or anything with a main window that lives in the dock, the tray lifecycle is overhead you do not need, and plain Electron is the shorter path.
How menubar() wires the tray, window and positioner
Calling menubar() returns a Menubar instance that exposes four Electron objects as properties: app, window, tray and positioner. The positioner is an instance of electron-positioner, which is the package's only runtime dependency according to package.json. Electron itself is declared as a peer dependency with the range >=9.0.0, so the Electron version in your project is the one that gets used.
Window placement is driven by the windowPosition option, documented as defaulting to trayCenter and trayBottomCenter on Windows, with explicit x and y values overriding it. The showOnAllWorkspaces option defaults to true, so on macOS the window is available on every workspace unless you turn that off. The showDockIcon option defaults to false, which is what keeps a tray app out of the dock.
Lifecycle is exposed as events rather than callbacks. ready fires when the tray icon has been created and initialized, and the README notes this is a different moment from Electron's own app ready event, which happens much earlier. Between create-window and after-create-window you get before-load, which the README suggests can be used for require("@electron/remote/main").enable(webContents). The show, hide and focus-lost events fire around the corresponding window calls, and after-close fires once the .window property has been deleted. That ordering is the part worth reading twice: if you attach a listener to the wrong event, you are holding a reference to a window that no longer exists.
Installing menubar and getting a tray window on screen
The README's installation step is a single package manager command. It uses yarn; the package is published on npm as menubar, so npm install works too, but the documented form is:
yarn add menubarThe usage section then starts from a fresh project and creates two files, an entry script and an HTML page:
yarn add menubar
touch myApp.js
touch index.htmlmyApp.js requires the package and calls the factory. The README's example is this, and it is the whole application:
const { menubar } = require('menubar');
const mb = menubar();
mb.on('ready', () => {
console.log('app is ready');
// your app code here
});With index.html filled in, you run the script through Electron rather than node:
electron myApp.jsWhat you should see is a tray icon, since the icon option defaults to IconTemplate.png inside the app source directory, and a window that opens on click. The README points at examples/hello-world for a working version of exactly this, and the repository also ships examples/arrow, examples/icon-animation and examples/native-menu. Two defaults are worth knowing before you debug anything: dir defaults to process.cwd(), and index defaults to file:// plus that directory plus index.html. If your HTML is not at the working directory root, set dir or index explicitly.
Tuning the window: preloadWindow, alwaysOnTop and icon sizing
The default window is 400 by 400, from the browserWindow width and height options. alwaysOnTop defaults to false, and the README states that when it is true the window will not hide on blur, which changes the interaction model from a menu that dismisses to a panel that stays. The focus-lost event exists for that configuration, firing when the user clicks away.
preloadWindow defaults to false. Turning it on creates the BrowserWindow before it is used, and the README is explicit about the trade: higher resource usage in exchange for a faster click response. For a tray app that is opened dozens of times a day, that is a real decision rather than a micro-optimisation.
Icon handling is documented in enough detail to matter. The README suggests starting at 20x20, and for retina screens supplying a second image at double size with @2x appended to the filename, so icon.png and [email protected], with Electron selecting the larger one automatically. If you pass your own tray instance instead, the icon option is ignored entirely. showOnRightClick, defaulting to false, swaps the window trigger from a normal click to a right-click, which is the option to reach for when a left click needs to do something else.
Where menubar is the wrong tool
The package is a window-lifecycle helper, not an application framework. None of its documented options or events covers packaging, code signing, auto-update or installer generation. If your release process needs any of those, you are adding a separate toolchain on top, and menubar contributes nothing to it.
Platform coverage is the second boundary. The README claims macOS, Windows and "most Linuxes" and defers to WORKING_PLATFORMS.md for details. That file is the authority, not the badge row in the README, and "most Linuxes" is a hedge rather than a guarantee. Tray support on Linux depends on the desktop environment and its status notifier implementation, so a distribution that works in one session may behave differently in another.
The third boundary is version drift. The compatibility table in the README maps menubar 9.x.x to Electron >=9.x.x with continuous type-checking against Electron 35.x.x through 43.x.x in CI, and menubar 8.x.x to Electron 8.x.x. Older rows map 7.x.x to Electron 7.x.x, 6.x.x to Electron >= 4.x.x < 7.x.x (not recommended for security reasons) and <= 5.x.x to Electron <= 3.x.x. If you are pinned to an old Electron, check the published table before assuming a current menubar release fits.
How menubar differs from writing Electron tray code directly
The direct alternative is Electron's own Tray and BrowserWindow APIs, which is what menubar wraps. The difference is not capability, since menubar exposes the underlying app, window and tray objects anyway. The difference is which decisions are already made for you.
In raw Electron you write the blur handler that hides the window, you compute the position under the tray icon, you pick the workspace behaviour, and you remember to suppress the dock icon. With menubar those arrive as defaults: showOnAllWorkspaces true, showDockIcon false, windowPosition defaulting to trayCenter. You can still change each one through the options object or through setOption and getOption after construction, but you start from a working tray app instead of an empty main process.
The cost is the extra layer. electron-positioner is a dependency you would not otherwise carry, and the event names are menubar's own rather than Electron's, so knowledge transfers only partially. For a project that needs one custom tray window, the wrapper is worth it. For a project that needs several windows with different behaviours, the abstraction starts fighting you, because the Menubar instance models exactly one window.
Maintenance, licensing and what an upgrade costs
The repository is not archived, and the last push was on 2026-07-30, which is recent. The repository has no published GitHub releases, so version history has to come from package.json, which lists the current version as 9.5.3, and from CHANGELOG.md in the repository root. The build is plain tsc behind a rimraf lib/ step, tests run through jest, and linting is biome. The published package ships only /assets and /lib, which means the TypeScript source is not in the tarball but types are, since package.json points types at lib/index.d.ts.
The upgrade surface is small because the runtime dependency count is one. The real upgrade cost is Electron, not menubar: Electron is a peer dependency, so bumping Electron is your decision and your testing burden, and the compatibility table is the reference for which menubar line has been checked against which Electron range.
Licensing is BSD-2-Clause, stated in both the LICENSE file and the license field of package.json. That is a permissive licence, and it places few conditions on redistribution beyond retaining the copyright notice and licence text. This is a description of what the repository declares, not legal advice; if you are shipping a commercial product, have your own counsel read the LICENSE file.
Editorial conclusion
Adopt menubar if you want a tray or menu bar window and already ship Electron: one call, one runtime dependency, and a documented event list for wiring window behaviour. Do not adopt it if you need a full application shell with auto-update, packaging or code signing, because none of its documented options or events covers that. Before you commit, verify that your target desktop appears in WORKING_PLATFORMS.md, and confirm the Electron version you build against falls inside the compatibility table, which the README states is continuously type-checked against Electron 35.x.x through 43.x.x for menubar 9.x.x.
Frequently asked questions
What is menubar and what does it actually do?
It is an npm package that provides boilerplate for building an Electron menubar or tray application. You point it at an index.html and it creates the tray icon, the browser window and the positioning between them.
How do I install menubar?
The README's installation step is yarn add menubar. Electron is a peer dependency, so it must be present in your project separately, and the README runs the app with electron myApp.js rather than node.
How do I use menubar on macOS?
The same code path works on macOS: call menubar(), listen for the ready event, and run the script through Electron. macOS-specific defaults include showOnAllWorkspaces set to true and showDockIcon set to false, so the app appears as a tray item rather than a dock icon.
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)Star history chart for your README
[](https://hysenlabs.com/projects/max-mapper-menubar)Was this useful?