| name | macos-menubar-app |
| description | Build a production-quality macOS menubar or notch app in SwiftUI — covering MenuBarExtra setup, sandbox entitlements, keyboard shortcuts, sound effects, accessibility permissions, and /critique-driven polish cycles. Use when starting or extending a macOS agent app that lives in the status bar or Mac notch area. |
| license | MIT |
| allowed-tools | Bash, Read, Write, Edit |
| compatibility | Codex, Claude Code, Cursor, GitHub Copilot, Windsurf, Kiro, and other Agent Skills compatible tools. Requires Xcode 15+, macOS 13+ deployment target, and an Apple Developer account for App Store distribution. |
| metadata | {"targets":["cursor"],"author":"Oleg Koval","tags":["macos","swiftui","menubar","notch","xcode","apple","sandbox"]} |
🤖 Auto-generated by weekly-pattern-learner · SwiftUI macOS menubar app pattern observed in FocusNotch session, Jun 2026 (364 user turns, ~48 MB transcript covering idea → spec → impl → critique cycles → App Store)
macOS Menubar / Notch App (SwiftUI)
Overview
Build a macOS app that lives in the menu bar or notch using SwiftUI's MenuBarExtra. Covers the full lifecycle: project setup, window management, entitlements, user interaction (keyboard shortcuts, sound), and App Store preparation.
When to Use
- User asks to build an app for the macOS menubar, status bar, or notch
- Adding a menubar presence to an existing macOS app
- Debugging sandbox rejections or entitlement errors in a SwiftUI app
Workflow
1. Project Setup
Create a new macOS App target in Xcode:
- Interface: SwiftUI
- App Sandbox: Enabled (required for App Store)
- Bundle ID:
com.yourname.AppName
- Deployment target: macOS 13+ (for
MenuBarExtra)
In Info.plist, set:
<key>LSUIElement</key>
<true/>
This hides the app from the Dock — the menu bar becomes the only entry point.
2. MenuBarExtra (SwiftUI native)
@main
struct FocusApp: App {
var body: some Scene {
MenuBarExtra {
ContentView()
.frame(width: 320, height: 480)
} label: {
Label("Focus", systemImage: "timer")
}
.menuBarExtraStyle(.window)
}
}
Use .menuBarExtraStyle(.window) for a popover-style panel. Use .menuBarExtraStyle(.menu) for a dropdown menu.
3. Notch Detection
On MacBooks with a notch, the safe area changes. Detect and adapt:
func isNotchPresent() -> Bool {
guard let screen = NSScreen.main else { return false }
let screenHeight = screen.frame.height
let visibleTop = screen.visibleFrame.maxY
return visibleTop < screenHeight
}
Position windows below the notch:
window.setFrameOrigin(NSPoint(
x: window.frame.origin.x,
y: NSScreen.main!.frame.height - window.frame.height - notchOffset
))
4. Sandbox Entitlements
Minimum .entitlements for a sandboxed menubar app:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
For focus/blocking features that need accessibility:
<key>com.apple.security.temporary-exception.apple-events</key>
<array>
<string>com.apple.systemevents</string>
</array>
5. Accessibility Permission (Focus Detection)
To detect which app is in focus (required for distraction blocking):
import ApplicationServices
func requestAccessibilityPermission() -> Bool {
let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true]
return AXIsProcessTrustedWithOptions(options)
}
Call this on first launch and prompt the user to grant access in System Settings → Privacy → Accessibility.
6. Sound Effects
import AVFoundation
class SoundPlayer {
private var player: AVAudioPlayer?
func play(_ filename: String, extension ext: String = "mp3") {
guard let url = Bundle.main.url(forResource: filename, withExtension: ext) else { return }
player = try? AVAudioPlayer(contentsOf: url)
player?.play()
}
}
Add sound files to the Xcode target (check Target Membership in file inspector).
For system sounds (no file needed):
NSSound.beep()
NSSound(named: .init("Hero"))?.play()
7. Keyboard Shortcuts
Global shortcuts (work when app is not focused) — requires Accessibility permission:
NSEvent.addGlobalMonitorForEvents(matching: .keyDown) { event in
if event.modifierFlags.contains([.command, .shift]) && event.keyCode == 36 {
}
}
Local shortcuts (SwiftUI):
.keyboardShortcut("s", modifiers: [.command, .shift])
8. Persistent State (AppStorage)
@AppStorage("timerDuration") private var timerDuration: Double = 25 * 60
@AppStorage("soundEnabled") private var soundEnabled: Bool = true
For complex state, use UserDefaults with a suite name to share across extensions.
9. Polish Cycle
After each implementation slice:
Optional skills: Steps 1 and 3 require /critique and /whimsy-injector from the agent-skills catalog. Install them via skillshare if not already available; skip these steps if not installed.
- Run
/critique — evaluate against Apple HIG, WCAG contrast, interaction consistency
- Fix all P0/P1 findings
- Run
/whimsy-injector on completion screens and idle states
- Test on a machine without a notch to verify layout
10. App Store Preparation
See olko:apple-store-submit for the full submission and rejection-handling workflow.
Pre-submission checklist:
xcodebuild archive -scheme <AppName> -archivePath build/<AppName>.xcarchive
codesign --verify --deep --strict build/<AppName>.xcarchive/Products/Applications/<AppName>.app
codesign -d --entitlements :- build/<AppName>.xcarchive/Products/Applications/<AppName>.app
Key Pitfalls
| Symptom | Cause | Fix |
|---|
| App appears in Dock | LSUIElement missing | Add to Info.plist |
| Menubar icon missing after build | Asset catalog misconfigured | Use PDF or SVG template image, tick Template Image |
| Crash on click | View not loaded in popover window | Add .frame() to MenuBarExtra content |
| Sound not playing | File not in target | Check Target Membership in file inspector |
| Accessibility prompt not shown | Missing entitlement | Add temporary-exception.apple-events or use XPC |
| Notch overlap | Hardcoded y-position | Detect visibleFrame vs frame delta |
Verification