macOS Development

macOS Menu Bar App with NSPopover and SwiftUI: the 2026 Complete Guide

May 2026 · 10 min read

Menu bar apps occupy a specific niche on macOS: utility apps that sit out of the way, surface a single number or status, and open a popover or window when clicked. AI Battery (Claude usage tracker), iStat Menus, Bartender, and Itsycal all fit this pattern. Building one in 2026 with SwiftUI is straightforward - but the architecture is split between AppKit (NSStatusItem, NSPopover) and SwiftUI (the actual UI), and getting the two to cooperate requires a specific pattern.

NSStatusItem and the AppDelegate setup

A menu bar app starts with no main window. The UI lives entirely in a popover anchored to a status bar item. The setup pattern uses @NSApplicationDelegateAdaptor to bridge SwiftUI's App protocol with AppKit's classic AppDelegate.

Inside AppDelegate's applicationDidFinishLaunching, create an NSStatusItem via NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength). Configure its button.image (typically an SF Symbol for monochrome rendering), set button.action to a selector on AppDelegate that toggles the popover.

Create the NSPopover separately. Set contentSize to your preferred popover dimensions (320x400 is common). Set behavior to .transient (closes when user clicks outside). Set contentViewController to an NSHostingController wrapping your SwiftUI root view. The NSHostingController(rootView:) is the bridge between AppKit and SwiftUI - it lets a SwiftUI view be hosted inside a popover. From the SwiftUI side, the popover view is just a regular SwiftUI View, with full access to @Observable models, .environment, and all the usual machinery.

Important detail. The Info.plist must set LSUIElement to YES. This hides the Dock icon, hides the menu bar items (File/Edit/etc.) for the running app, and lets it run purely as a menu bar utility. Without this, you'll see a Dock icon and a useless menu bar.

Keeping the popover open, launch at login, icon sizing

Keeping the popover open. With behavior = .transient, the popover closes when the user clicks outside it. This is the standard macOS behaviour and what most users expect. But sometimes you want it to stay open - for example, while a setting is being edited.

The fix: switch the popover to .semitransient while interactive, then back to .transient when done. Or use .applicationDefined and manage close behaviour entirely yourself based on focus events.

Launch at login. The modern API is SMAppService (replacing the older SMLoginItemSetEnabled). Call SMAppService.mainApp.register() to enable launch at login, .unregister() to disable. No external helper bundle required (unlike the old API). The user gets a system prompt the first time to authorise login-item registration.

Menu bar icon sizing. The menu bar height changes between macOS versions and depends on whether the user has the menu bar set to dark mode. The reliable approach: use an SF Symbol with isTemplate = true and let macOS handle dark mode + retina scaling automatically.

Custom (non-SF-Symbol) icons should be PDF or PNG with template mode enabled. The icon must be monochrome for template mode to work - colour PNGs render as colour and break dark mode adaptation. Size around 18x18 points works well across macOS versions.

Dynamic status text. For status indicators that show a number (e.g. battery percentage, current price), you can set button.title in addition to or instead of button.image. The system handles font and colour automatically. For combined image + text, set both and macOS renders them inline.

Quick comparison

Option Best for Cost / effort Notes
NSStatusItem Bar entry variableLength Set image + action
NSPopover Popup UI .transient NSHostingController for SwiftUI
LSUIElement Hide Dock Info.plist YES Pure menu bar app
SMAppService Launch at login Modern API No helper bundle

Common pitfalls and how to avoid them

Across every domain this article touches, the same shape of mistake recurs. Practitioners new to the field overweight the most visible piece of the system — the screenshot, the paywall, the exam question, the headline price — and underweight the underlying constraint that actually determines outcomes.

The five most common failure modes:

  1. Optimising for the demo, not the durability. A working demo in a controlled environment proves nothing about reliability under real conditions. In iOS development, an in-app purchase flow that works in the Xcode Simulator says nothing about how it behaves in App Store sandbox with network latency and Ask to Buy approvals. In an exam, a 100% score on an untimed quiz tells you nothing about whether you can do 49/50 in 45 minutes with no second guesses. Build for the hardest realistic case from the start.
  2. Skipping the first-principles documentation. Every system has a canonical specification. App Review Guidelines for iOS, the official EU regulations for tax deductibility, the CITB question bank for CSCS, the OMIE market rules for Spanish electricity. Reading them takes a few hours but saves weeks of wrong-direction work. Secondary sources (blogs, tutorials, this article included) are useful as orientation but never authoritative.
  3. Ignoring the rate limit. Every external system has rate limits — explicit (APNs silent push throttling, RevenueCat API quotas, exam retake fees) or implicit (App Review patience, customer attention spans, your own working memory). Plan around them. A workflow that requires more rate-limited operations than the system allows will fail in production, not on day one but during the first stress event.
  4. Underweighting localisation and regional variation. What is true for Germany is not always true for Italy. What is true for English-speaking users is not always true for Japanese ones. What is true for the UK CSCS test is not always true for the Irish equivalent. Always check the local rule before applying a general one.
  5. Treating the documentation as static. Apple updates App Review Guidelines. The Bundeslaender change Schonzeiten. OMIE adjusts market clearing algorithms. Set up a periodic review (quarterly is enough for most things) and re-read the canonical sources. Workflows that worked perfectly a year ago can be silently broken today.

None of these are dramatic. The dramatic mistakes (catastrophic bugs, audit findings, exam failures) are the visible tip of a longer-running iceberg of small misses. Catching the small misses is what separates routine outcomes from problematic ones.

Key takeaways

  • NSStatusItem — Bar entry. Set image + action.
  • NSPopover — Popup UI. NSHostingController for SwiftUI.
  • LSUIElement — Hide Dock. Pure menu bar app.
  • SMAppService — Launch at login. No helper bundle.

The pattern that runs through every section above: start with the constraint, not the wishlist. In an exam, the constraint is the question bank and the pass mark. In an electricity market, it is the auction clearing rule. In a tax workflow, it is the receipt-retention requirement. In a code architecture, it is the platform's design decision (StoreKit's transaction lifecycle, App Review's guideline, APNs's authentication model). Get the constraint right and the rest follows.

The opposite failure mode — designing for an aesthetic ideal, then trying to retro-fit the constraint — is the most common cause of wasted work in every domain covered here. A beautiful paywall that hangs in sandbox is rejected at App Review. A polished freelancer expense report that lacks receipts is disallowed by the tax office. A study plan that ignores the actual question distribution leaves the candidate stuck below the pass mark.

The practical recommendation: read the official rules of whatever system you are operating in, extract the binding constraints, and treat them as inputs to the design — not afterthoughts. Every section of this article is the application of that principle to a specific domain.

FAQ

Should I use a popover or a window for the menu bar UI?
Popover for utility apps (status display, quick settings). Window for apps that need persistent state or background data (audio mixer, file viewer). NSPopover is more common.

Can I show a menu instead of a popover?
Yes. NSStatusItem has a .menu property - set it to an NSMenu and clicking the icon shows that menu directly. Best for short option lists. For anything richer (charts, custom UI), use a popover.

How do I update the menu bar icon dynamically?
Set statusItem.button.image periodically. For a status indicator (e.g. battery percentage), you can compose an NSImage at runtime by drawing text into an NSImage of fixed size.

Does my menu bar app need code signing for distribution outside the App Store?
Yes. For Developer ID distribution, the app must be signed with a Developer ID certificate and notarised by Apple. The notarisation is automatic via xcrun notarytool.

Further reading and references

The references below cover the official sources for the rules cited in this article. Where applicable, they include the canonical documentation, regulatory text, or vendor-provided guides. For each one, prefer the official source over secondary commentary — secondary sources go stale fast and frequently misquote the binding rule.

  • Official documentation of the system in question (linked from each app or service's own help centre).
  • Apple Developer Documentation for any iOS/macOS reference — the WWDC session videos and the corresponding Human Interface Guidelines pages are the authoritative source.
  • For EU regulatory questions (taxation, data protection, energy market structure), consult the relevant national authority — most publish their guidance in English.
  • For Spain and Italy energy market data, OMIE and GME both publish full historical price series in CSV format from their public websites — no API key required.
  • For UK CSCS prep, the CITB publishes the official question bank book each year — buy a current copy if you want the authoritative source.

If you find a contradiction between this article and an official source, the official source wins. Article rules of thumb are summaries — they have edge cases, exceptions, and regional variations that the source documents specify exactly.

macOS menu bar app development

Native macOS development - NSStatusItem, NSPopover, SwiftUI integration. AI Battery and Mbox Splitter Pro are both menu bar apps.

Book a discovery call

Related Posts

iOS WidgetKit guide
TimelineProvider, entries, widget families.
Swift Package Manager for multi-platform
Sharing code across iOS and macOS targets.
← All blog posts

Build a menu bar utility

macOS menu bar apps from someone who ships them and uses them daily.

Book a discovery call