Build a menu bar app for macOS: the parts you need
A menu bar app is one of the smallest useful things to build on macOS. There is no window to lay out, no document model, no state restoration. A symbol goes in the strip at the top of the screen, a menu or a small panel drops out of it, and the app does one job well.
The small surface hides a set of decisions that are awkward to change later. Which framework owns the status item. Whether the app shows in the Dock. What happens when there is no room for the item at all. Getting those three right at the start costs an afternoon. Getting them wrong costs a rewrite, because each one reaches into the app lifecycle rather than sitting at the edge of it.
Two ways to put an item in the strip
AppKit has owned this since the beginning. NSStatusBar vends items into the system menu bar, and each one is an NSStatusItem.
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
item.button?.image = NSImage(systemSymbolName: "moon.fill", accessibilityDescription: "Sleep")
item.menu = buildMenu()
SwiftUI added a scene type for the same job in macOS 13, described by Apple as "a scene that renders itself as a persistent control in the system menu bar."
@main
struct UtilityApp: App {
var body: some Scene {
MenuBarExtra("Utility App", systemImage: "hammer") {
AppMenu()
}
}
}
| NSStatusItem | MenuBarExtra | |
|---|---|---|
| Framework | AppKit | SwiftUI |
| Available from | Long predates both | macOS 13 |
| Lifetime control | Created and released by code at any point | Declared as a scene, inserted with isInserted |
| Custom drawing | Full access to the button and its view | Through the label view |
| Panel style | Build and manage an NSPopover |
.menuBarExtraStyle(.window) |
| Removal by the user | Handled through behavior |
Removing the extra terminates a menu bar only app |
The choice is less about capability than about how much of the app is already SwiftUI. A pure SwiftUI app that needs one dropdown should use the scene. An app that needs to change the item's length while it runs, swap the view under the button, or keep several items with independent autosave identities is easier to write against AppKit, and mixing the two in one app is normal.
One behaviour catches people out on the SwiftUI side.
An app that only shows in the menu bar will be automatically terminated if the user removes the extra from the menu bar. Source: developer.apple.com, read September 18, 2026
That is reasonable for a single purpose utility and wrong for anything with background work to finish. Check it against the intended design before committing.
The item is a button, and the button is the interface
NSStatusItem exposes a real NSButton through its button property, created automatically. Most of the work happens there: the image, the title, the appearance when highlighted, and the target and action if the item should respond to a click rather than drop a menu.
Length comes in two documented forms. variableLength is "a status item length that dynamically adjusts to the width of its contents," which is what a text readout such as a battery percentage or a timer needs. squareLength is "a status item length that is equal to the status bar's thickness," which is what a plain symbol should use so it sits on the same rhythm as every system icon beside it.
Three properties matter more than their obscurity suggests.
isVisible is "a Boolean value indicating if the menu bar currently displays the status item." Setting it to false removes the item from view without tearing down any of the app's state. This is the mechanism behind a preference that lets the user turn the icon off, and Apple asks for exactly such a preference.
autosaveName has existed since macOS 10.12 and is "a unique name for saving and restoring information about a status item." Without one, the system picks a name, which is fine for a single item app. Apple's guidance is explicit for anything larger: "Applications with multiple status items should set an autosave name after creating each item." Position, and whether the user hid the item, are persisted against that name.
behavior opts into user removal. It is an option set with two members, removalAllowed and terminationOnRemoval. By default it is empty, meaning the item cannot be dragged out of the bar at all. Opting into removal is the polite choice for anything that is not the app's only interface.
Decide early whether the app appears in the Dock
An app that lives only in the menu bar usually should not appear in the Dock or the application switcher. One key in the information property list controls that.
A Boolean value indicating whether the app is an agent app that runs in the background and doesn't appear in the Dock. Source: developer.apple.com, read September 18, 2026
Setting LSUIElement to true has consequences beyond the Dock icon. The app never owns the app menus at the left of the menu bar, so there is no File menu, no Edit menu, and no standard place for a Preferences item. Everything the user can reach must hang off the status item's own menu, including the settings window and the quit command. Forgetting a quit item in an agent app leaves people with no way to stop it short of Activity Monitor.
The flag can be flipped at runtime through the activation policy, which is how apps that have both a main window and a menu bar presence switch between the two modes. That path is workable but adds state to manage, so it is worth being sure the app really needs both.
Space in the menu bar is not guaranteed
This is the constraint that gets planned for last and causes the most support mail. Apple has been direct about it in the NSStatusBar documentation for years.
Use status items sparingly and only if the alternatives (such as a Dock menu, preference pane, or status window) are not suitable. Because there is limited space in which to display status items, status items are not guaranteed to be available at all times. For this reason, do not rely on them being available and always provide a user preference for hiding your application's status items to free up space in the menu bar. Source: developer.apple.com, read September 18, 2026
Two design rules follow. The app must remain fully functional when its item is not on screen, which means no feature may be reachable only by clicking the icon. And the app should ship the preference Apple asks for, wired to isVisible, rather than assuming the user will accept the icon forever.
On a MacBook the notch sits in the middle of the strip and removes usable width, so a user with twenty resident utilities has already run out of room before the new app installs. macOS 27, released on September 14, 2026, added a double arrow control that appears when items are being covered and temporarily shifts them to the left of the notch so they can be clicked. It makes the crowded case survivable. It does not create space, and many users solve the underlying problem with a menu bar manager for macOS that keeps icons installed but out of the visible strip. Building an item that behaves well when hidden is therefore not an edge case. The comparison page shows how differently those tools treat a hidden item, which is worth reading before assuming the icon will always be clickable.
One accessibility setting is worth testing against before release. System Settings, Accessibility, Display has a Text section containing Menu bar size, described by Apple as a way to "set the font size in the menu bar to Large, or back to Default." Turning it on makes the strip taller and the text bigger, and a custom drawn status item that assumed a fixed height will look wrong immediately. Template images and a square length item adapt on their own. Anything drawing its own view needs a pass with that setting enabled, alongside a pass in both light and dark appearance.
What macOS 27 changed for this kind of app
Two changes land directly on menu bar developers.
The first affects every menu the app drops.
In macOS 27.0, menu bar and context menus present a reduced set of menu item images, similar to the behavior prior to macOS 26.0. By default, NSMenu hides all menu item symbol images. Source: developer.apple.com, read September 18, 2026
Menus designed around a symbol next to every row will look different after the upgrade, and non-symbol images such as colour swatches still appear. Anything that relied on a symbol to carry meaning needs the meaning moved into the text.
The second is a new API. expandedInterfaceDelegate on NSStatusItem arrived in macOS 27 and "manages the lifecycle of the status item's expanded interface," showing something such as a popover positioned beneath the item and dismissing it again. Apple's note adds that status items which assign a menu to their button do not receive these callbacks, because the system handles the expanded menu automatically. For a panel style item, this replaces a pile of hand written popover and event monitor code with a delegate. It also raises the deployment target, so a shipping app will keep its existing path for older systems for some time.
Launching at login without a legacy helper
Most menu bar apps want to come back after a restart. Since macOS 13 there is one supported route for this, SMAppService, described as "an object the framework uses to control helper executables that live inside an app's main bundle." Apple's guidance is unambiguous about which era to write for: "In macOS 13 and later, use SMAppService to register and control LoginItems, LaunchAgents, and LaunchDaemons as helper executables for your app."
The usable surface is small. register() and unregister() turn the login item on and off, status reports where it stands, and openSystemSettingsLoginItems() sends the user to the right pane in System Settings when they need to approve or inspect it. That last call matters, because the user can revoke the login item from System Settings at any time and the app needs to reflect that state honestly instead of silently re-registering.
Shipping it
Distribution outside the App Store means a Developer ID signature and notarization, without which Gatekeeper will refuse the first launch on any machine other than the build machine. That cost is fixed and unavoidable, and it is worth paying before the first external tester rather than after.
One platform change is worth noting for anything that still ships an Intel slice. macOS 27 is the first macOS release to run exclusively on Macs with Apple silicon, and it is the last to carry full Rosetta 2 functionality. New projects can reasonably target arm64 only. Existing ones need a decision about how long the Intel slice stays in the build.
What to build first
Start with the item, the menu, and a working quit command, with LSUIElement set and isVisible wired to a preference from day one. Those four pieces decide the shape of everything after them. Before designing the icon, spend ten minutes with a crowded menu bar on a laptop and watch what actually happens to a new item, which is easiest to do with something like Koffret already installed.
Frequently asked questions
Should a new Mac menu bar app use MenuBarExtra or NSStatusItem?
If the app is already SwiftUI and needs one dropdown or one small panel, the scene type is less code and less lifecycle to manage. If the item's length changes at runtime, the app owns several items, or the button needs a custom view, AppKit gives more direct control. Mixing both in one app is common and supported.
Why does a menu bar app have no Preferences item in the app menu?
Because an agent app has no app menus at all. Setting the agent flag removes the Dock icon and the row of menus at the left of the menu bar, so settings, about, and quit all have to be reachable from the status item's own menu. Any command left out of that menu is unreachable.
What happens if there is no room left in the menu bar for the item?
Apple states plainly that status items are not guaranteed to be available at all times because the space is limited. The app should keep working with the item off screen, and should offer a preference to hide the icon. Designing on the assumption that the icon is always visible and always clickable will break on a laptop with a notch.
How should a menu bar app start at login on current macOS?
Through SMAppService, available from macOS 13, using register and unregister and reading the status property to reflect the real state. The older login item helper APIs are the thing being migrated away from. Offer a way to open the Login Items pane in System Settings, because the user can revoke the registration there.
Do menu items still show symbols in macOS 27?
Not by default. Apple's release notes state that menus present a reduced set of menu item images and that symbol images are hidden by default, which reverses the macOS 26 behaviour. Non-symbol images still appear, so colour swatches and thumbnails are unaffected, but a menu that depended on symbols to distinguish rows needs its labels rewritten.