---
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: [_source-only]
  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:

```xml
<key>LSUIElement</key>
<true/>
```

This hides the app from the Dock: the menu bar becomes the only entry point.

### 2. MenuBarExtra (SwiftUI native)

```swift
@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:

```swift
func isNotchPresent() -> Bool {
    guard let screen = NSScreen.main else { return false }
    // The notch area is excluded from visibleFrame top
    let screenHeight = screen.frame.height
    let visibleTop = screen.visibleFrame.maxY
    return visibleTop < screenHeight
}
```

Position windows below the notch:

```swift
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
<?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/>
    <!-- Add only what the app actually uses: -->
    <!-- <key>com.apple.security.network.client</key> -->
    <!-- <true/> -->
</dict>
</plist>
```

For focus/blocking features that need accessibility:

```xml
<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):

```swift
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

```swift
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):

```swift
NSSound.beep()
// or
NSSound(named: .init("Hero"))?.play()
```

### 7. Keyboard Shortcuts

Global shortcuts (work when app is not focused); requires Accessibility permission:

```swift
NSEvent.addGlobalMonitorForEvents(matching: .keyDown) { event in
    if event.modifierFlags.contains([.command, .shift]) && event.keyCode == 36 { // ⌘⇧↩
        // Handle shortcut
    }
}
```

Local shortcuts (SwiftUI):

```swift
.keyboardShortcut("s", modifiers: [.command, .shift])
```

### 8. Persistent State (AppStorage)

```swift
@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.

1. Run `/critique`: evaluate against Apple HIG, WCAG contrast, interaction consistency
2. Fix all P0/P1 findings
3. Run `/whimsy-injector` on completion screens and idle states
4. 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:

```bash
# Validate archive
xcodebuild archive -scheme <AppName> -archivePath build/<AppName>.xcarchive

# Check sandbox
codesign --verify --deep --strict build/<AppName>.xcarchive/Products/Applications/<AppName>.app

# Verify entitlements
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

- [ ] App does not appear in Dock (`LSUIElement` = true)
- [ ] MenuBarExtra icon renders as template image (respects dark/light mode)
- [ ] Sound plays on timer events
- [ ] Keyboard shortcuts respond both globally and locally
- [ ] App passes `codesign --verify --deep --strict`
- [ ] Layout correct on M1 MacBook (no notch) and M3 MacBook Pro (notch)
