NSStatusItem on macOS: Menu Bar Space, Retention, and User Control
Build considerate AppKit menu bar utilities with retained NSStatusItems, adaptive lengths, autosave identity, accessible menus, and graceful space handling.
An NSStatusItem is a small entry point into an application, not a guaranteed permanent slot in the menu bar. The system has limited space, people can hide items, and other menu bar content competes for room. A well-designed utility uses a status item only when it provides real ongoing value, keeps a second path to important settings or actions, and responds correctly when the item is hidden or its owner is released.
AppKit creates a status item through NSStatusBar.system.statusItem(withLength:). The status bar does not retain the item for the application. Keep a strong reference for as long as it should exist, and explicitly remove it when the feature or application lifecycle requires that. Losing the reference can remove the item from the menu bar without an obvious error, which is a common cause of “it worked once” behavior.
Retain the item and keep its responsibilities small
An app delegate or a dedicated controller can own the item. Avoid putting application state, networking, and persistence directly into a custom button view. Let the controller route user intent to a model or service, and make the status item a presentation surface.
import AppKit
@MainActor
final class StatusBarController: NSObject {
private let item = NSStatusBar.system.statusItem(
withLength: NSStatusItem.variableLength
)
override init() {
super.init()
item.autosaveName = "com.example.utility.status-item"
item.button?.image = NSImage(
systemSymbolName: "waveform",
accessibilityDescription: "Audio utility"
)
item.button?.toolTip = "Audio utility status and controls"
item.menu = makeMenu()
}
func remove() {
NSStatusBar.system.removeStatusItem(item)
}
}
The bundle-like autosave name should be stable and unique across the app’s status items. AppKit persists and restores visibility when an autosave name is set. If an app creates multiple items, assigning distinct names avoids ambiguous saved state. A hidden item is not a broken item; surface a preference or a normal application menu action that lets the user restore it.
Choose a length strategy
Use the square length for a symbol-sized item or variable length when text changes based on live state. A fixed custom point width is rarely a good fit for a changing locale, accessibility text size, or changing status string. Keep menu-bar text concise, and do not rely on a long label being visible in every menu bar configuration.
The button’s image and title should communicate the current state at a glance. Keep detailed status in the menu or a dedicated window. For state that can change rapidly, avoid updating the title on every sample; aggregate updates or render only meaningful state transitions. Excessive menu-bar churn is distracting and wastes redraw work.
If you assign a custom NSView to item.view, that custom view takes responsibility for its own appearance and behavior. It no longer inherits the ordinary button’s click handling and visual conventions automatically. Implement clear hit testing, accessible information, pointer states, drawing invalidation, and action delivery. Prefer the standard status item button when a custom view is not needed.
Menu and target-action behavior
An NSStatusItem can present an NSMenu or send a target-action message when its button is clicked. Choose one interaction model deliberately. A menu is a good fit for a short list of commands and status; a custom view can support richer interaction but increases accessibility and lifecycle obligations. Do not install a menu and also depend on a click action without testing which interaction AppKit delivers for the chosen setup.
Menus should use concise, actionable labels and reflect the latest model state when opened. Avoid doing network calls or blocking disk reads during menu construction. If status must be refreshed, update it asynchronously before the menu opens and display a truthful loading or stale state. A checkmark is a statement about current state, not merely the last requested state; update it after the operation reports its result.
Every action should remain available somewhere else if hiding the status item would otherwise make the app unusable. Place preferences in the standard app menu or a normal settings window. If the item supports a background utility, explain why it exists and provide a quit command that performs expected cleanup without leaving orphaned work.
Space, visibility, and persistence
Apple documents that status items are not guaranteed to remain available at all times because menu bar space is limited. Provide a user-facing preference to hide the item. Observe visibility changes if the product needs to update its own UI, but do not interpret isVisible == true as proof that the item is currently visible on screen: the system may temporarily hide it when space is insufficient.
Visibility persistence is not the same as application data persistence. A status item can restore its hidden/shown preference, but the application should reconstruct any menu state from its model. On app launch, create the item when the feature is enabled, configure its autosave name, and then let the system restore visibility. Avoid racing restoration by immediately forcing isVisible = true every launch.
When preferences disable the item, remove it or hide it according to the feature lifecycle. If removing it, retain enough controller state to create it again when the user re-enables the feature. If hiding it, ensure an independent route can show it. Test installation updates and preference migration so a new autosave name does not unexpectedly discard a user’s visibility choice.
An item that has been created but is currently hidden should not be recreated on every visibility notification. Treat the controller as a small state machine with separate states for feature disabled, item created and user-hidden, system-constrained visibility, and visible. This distinction prevents an observation callback from accidentally undoing the user’s hide preference. If the app offers a “Show in Menu Bar” preference, have that preference update the desired state and let a single controller own the actual NSStatusItem mutation.
Keep the menu useful when a background operation is unavailable or still running. Disable only the commands that cannot currently be performed, and include a concise status description so a user can understand why. A menu should not become a miniature settings application with dozens of items; use it as a fast summary and route complex configuration to a standard window. If a long-lived operation is cancellable, expose cancellation without making the menu item appear complete before the work has actually stopped.
For an app with more than one status item, create each item in a deterministic order, assign separate autosave names, and test that removing one does not alter the other’s visibility or menu. During termination, stop timers and observation tokens before releasing the controller. A notification handler that continues to update a removed item’s view is a lifecycle bug even if the status item no longer appears on screen.
Accessibility and interaction quality
A status item icon needs a concise accessibility description. A symbol without a title or accessible label may be visually recognizable to its designer but meaningless to VoiceOver. Every menu command should have an appropriate keyboard equivalent where standard conventions provide one, and the menu should be navigable using keyboard and VoiceOver.
Test a wide menu bar with many items, a narrow display, multiple displays, full-screen apps, and different system appearances. If the icon is a template image, verify its contrast in light and dark appearances. Do not use color as the only status signal; provide a text or shape cue. If a custom item has a popover or expanded interface, manage its presentation and dismissal explicitly and restore focus to a sensible location when it closes.
Verification checklist
Test item creation and removal, owner retention, saved hidden state, user toggling, missing symbol fallback, slow service responses, and app quit while a menu is open. Verify no background polling is tied to menu opening and that the action does not block the main thread. Confirm an item hidden for lack of space is not treated as a user disabling the feature.
A production status item has a clear ongoing purpose, stable retained ownership, a unique autosave identity, adaptive content, an alternate path to critical functionality, and an accessible interaction model. The menu bar is shared workspace, so restraint is part of correctness.
Related:
- How to Automate Repetitive Tasks on macOS with Shortcuts and Automator
- Unified Logging: How os_log Replaced syslog on macOS
Sources: