# Macos App Design

> Design and build OpenAI-Codex-quality native macOS apps with the polish of Linear, Things 3, Granola, Notion Calendar, Arc Browser, Raycast, and Apple's own apps. Use this skill whenever the user is building, reviewing, or polishing a native Swift/SwiftUI Mac app — including AI coding tools, productivity apps, note-taking apps, design tools, dev tools, menu-bar utilities, or document-based apps. Targets macOS 14 (Sonoma) minimum and especially macOS 26 (Tahoe / Liquid Glass). Triggers on: macOS, Mac app, native Mac, SwiftUI Mac, AppKit, NSWindow, NSToolbar, NSStatusItem, NavigationSplitView, sidebar, toolbar, menu bar, MenuBarExtra, traffic lights, multi-window, document-based app, NSDocument, .commands, .keyboardShortcut, .onHover, .contextMenu, three-column layout, Inspector pattern, Liquid Glass macOS, Tahoe, command palette, Cmd-K, Dock badge, Sparkle, notarization, Developer ID, code editor, syntax highlighting, SF Mono, Stage Manager, Continuity, Spotlight, Quick Look, Apple Intelligence.

- Skill: `heyimjames/macos-app-design` (Agent Skill)
- Install (CLI): `npx skillmds@latest add heyimjames/macos-app-design`
- Raw SKILL.md: https://api.skillmd.com/api/skills/heyimjames/macos-app-design/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: heyimjames (https://skillmd.com/u/heyimjames)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/heyimjames/macos-app-design

---


# macOS App Design — Design Engineering Skill

A taste guide for building macOS apps that feel like they were built by people who use Macs all day. Every value below is opinionated and specific — drawn from studying OpenAI's Codex app (the modern reference for AI-powered Mac apps), Linear's Mac client, Things 3, Granola, Raycast, Notion Calendar, Arc Browser, and Apple's own first-party apps.

This skill assumes a native Swift/SwiftUI Mac app, targeting macOS 14 (Sonoma) minimum and ideally macOS 26 (Tahoe — Liquid Glass). It pairs with the iOS skills in this marketplace — many polish principles transfer, but **Mac is its own platform with its own conventions**, and ignoring them is the single most common way Mac apps feel "off."

---

## Output format — required

When this skill is invoked to review macOS code or recommend changes, **always output recommendations as a markdown table** with three columns:

| Before | After | What this changes |
| --- | --- | --- |
| The current code, value, or approach (quote the user's actual code when possible) | The recommended replacement — **specific**, with exact values | One sentence on what the user will *see, feel, or experience* differently |

Three rules:
1. **Before** quotes the user's actual code where possible.
2. **After** is specific. Exact NSWindow sizes, exact keyboard shortcuts, exact toolbar item placements.
3. **What this changes** is *experiential or visual*, not abstract.

Output ONE table with multiple rows for multi-recommendation reviews. Use `—` for Before if the user hasn't implemented that thing yet.

**Examples drawn from this skill:**

| Before | After | What this changes |
| --- | --- | --- |
| Single-window app with everything in one view | `NavigationSplitView { sidebar } content: { content } detail: { detail }` with `.navigationSplitViewColumnWidth(min: 200, ideal: 220)` | Three-column layout reads as a proper Mac app instead of a stretched iOS app — Mac users have decades of muscle memory for the sidebar pattern |
| Button with `.onTapGesture` only | `Button(action: ...) { ... }.keyboardShortcut("n", modifiers: .command)` | Action gains Cmd-N keyboard binding — Mac users expect every meaningful action to have a shortcut |
| `Color.gray` for sidebar background | `.background(.regularMaterial)` (or `.background(.bar)` on macOS 26) | Sidebar picks up Liquid Glass translucency — content underneath shows through subtly, matches Finder/Mail/Notes |

This format is required for every recommendation output by this skill.

---

## Philosophy

> An iOS app on a Mac feels embarrassed. A web app in an Electron shell feels apologetic. A real Mac app feels at home.

The Codex Mac app is the right modern reference because it answers a question every AI tool builder is asking: "what does an AI coding tool *look like* as a native Mac citizen?" The answer it gives:

- **Sidebar**: conversations + projects, behaves exactly like Mail's mailbox list. Drag to reorder. Right-click for context menu. Type-to-select.
- **Toolbar**: minimal, unified with title bar, only the actions you actually need (new, search, share, settings).
- **Command palette**: `Cmd-K` opens an in-app palette — same gesture as Raycast, Linear, Things 3.
- **Code rendering**: SF Mono in code blocks, syntax-highlighted, with hover-to-copy.
- **Multi-window**: open a project in its own window. Each window has independent state.
- **Menu bar**: full menu hierarchy. Every action is in a menu. Every menu item has a shortcut.
- **Keyboard-first**: you can use the entire app without touching the trackpad.

That's the bar.

### Three pillars

**1. Mac convention over invention.** Mac users have 20+ years of muscle memory for sidebars, toolbars, menu bars, keyboard shortcuts, and traffic lights. Violating these conventions to be "different" is the #1 way Mac apps lose trust. If you're not sure how a control should behave, look at Mail, Notes, Finder, or System Settings first.

**2. Keyboard-first, mouse-enhanced.** Mac is fundamentally a keyboard machine. The trackpad is enhancement, not core. Every meaningful action gets a keyboard shortcut. Every list supports type-to-select. Every interactive element supports `Tab` focus. The trackpad adds hover states, scroll, swipe gestures — but never *replaces* the keyboard.

**3. Density without clutter.** Mac screens are big — 13-inch up to 32-inch displays. You can show more information than on iOS without the screen feeling busy, but only if you respect spatial hierarchy. Linear's Mac client shows ~3× more information per screen than its iOS app and feels *less* busy because the layout respects Mac conventions (sidebars, columns, fixed regions).

The pixel-pushers' rules:
- **The menu bar is sacred.** A Mac app without a meaningful menu bar feels broken. Even a single-purpose utility should have File / Edit / View / Window / Help.
- **The traffic lights are part of your design.** Don't hide them. Don't move them more than necessary. Don't replace them.
- **The keyboard shortcut hint is part of the UI.** Mac users *look at* `⌘ N` in menus and tooltips to learn the app.
- **Hover is information.** Every clickable element should have a hover state. Mac users move the mouse to discover.
- **Window restoration is not optional.** When the user reopens the app, every window should be exactly where they left it, with exactly the content they had.

---

## Section index

| Section | Topic |
| --- | --- |
| [§1](#1-window-architecture) | Window architecture — sizes, traffic lights, title bars, restoration |
| [§2](#2-the-sidebar-pattern) | The sidebar pattern — NavigationSplitView, three columns |
| [§3](#3-toolbars) | Toolbars — unified style, item placement, principal actions |
| [§4](#4-the-menu-bar) | The menu bar — required menus, item conventions, custom additions |
| [§5](#5-keyboard-shortcuts--focus) | Keyboard shortcuts & focus — every action has one |
| [§6](#6-the-command-palette-cmd-k) | The command palette (Cmd-K) — Raycast/Linear/Codex pattern |
| [§7](#7-mouse--trackpad--hover-states) | Mouse, trackpad & hover states |
| [§8](#8-context-menus-right-click) | Context menus (right-click) |
| [§9](#9-multi-window--document-architecture) | Multi-window & document architecture |
| [§10](#10-status-bar--menubarextra-utilities) | Status bar & MenuBarExtra utilities |
| [§11](#11-liquid-glass-on-macos-tahoe--macos-26) | Liquid Glass on macOS (Tahoe / macOS 26) |
| [§12](#12-typography-on-mac) | Typography on Mac |
| [§13](#13-color--material) | Color & material — semantic, OKLCH, materials |
| [§14](#14-spacing--density) | Spacing & density |
| [§15](#15-drag-and-drop) | Drag and drop |
| [§16](#16-code-app-specifics-syntax-highlighting-monospace-terminals) | Code-app specifics — syntax, monospace, terminals, file trees |
| [§17](#17-settings-window) | Settings window |
| [§18](#18-about-help--app-shortcuts) | About, Help, & App Shortcuts |
| [§19](#19-spotlight-quick-look--continuity) | Spotlight, Quick Look & Continuity |
| [§20](#20-stage-manager-spaces--mission-control) | Stage Manager, Spaces & Mission Control |
| [§21](#21-notifications--focus-modes) | Notifications & Focus modes |
| [§22](#22-app-icon--dock) | App icon & Dock |
| [§23](#23-app-intents--shortcuts) | App Intents & Shortcuts on Mac |
| [§24](#24-performance--promotion) | Performance & ProMotion |
| [§25](#25-distribution-app-store-vs-developer-id--sparkle) | Distribution — App Store vs Developer ID + Sparkle |
| [§26](#26-the-mac-polish-checklist) | The Mac polish checklist |
| [§27](#27-anti-patterns) | Anti-patterns |

---

## 1. Window architecture

The window is the unit of a Mac app. Get this right and the rest follows.

### Window sizes

| Window | Default min size | Use |
| --- | --- | --- |
| Main app window | 1000 × 700pt | Most apps; supports sidebar + content + detail |
| Compact app window | 720 × 480pt | Utility apps, single-purpose tools |
| Inspector / floating | 360 × 600pt | Side panel for properties, drawn alongside main |
| Settings window | 580 × 420pt (varies by panel content) | Settings with `.commands` integration |
| About panel | 400 × 480pt (system-default size for `orderFrontStandardAboutPanel`) | Use the system about panel — don't roll your own |

```swift
WindowGroup(id: "main") {
    ContentView()
}
.defaultSize(width: 1100, height: 720)
.windowResizability(.contentMinSize)  // resize down to min content size, not arbitrarily small
.windowToolbarStyle(.unified)          // modern look — toolbar shares title bar
```

### Traffic lights (close / minimize / zoom)

The three colored dots in the top-left are NOT decoration — they're part of macOS muscle memory.

Rules:
- **Never hide them.** A Mac app without traffic lights feels broken.
- **Default position is correct.** Don't move them just to be different.
- **Custom positioning** is acceptable for unique layouts (e.g., Arc Browser's sidebar — they shift down 8pt because the sidebar starts at top):

```swift
.windowStyle(.hiddenTitleBar)  // hides default title, you draw your own
.toolbar(.hidden, for: .windowToolbar)
```

For most apps, use `.windowToolbarStyle(.unified)` — the toolbar shares the title bar area, traffic lights live at the natural top-left, the title text floats inline.

### Title bars

Three styles:

| Style | When |
| --- | --- |
| `.windowToolbarStyle(.unified)` | DEFAULT for modern Mac apps — toolbar + title share one bar (like Notes, Mail) |
| `.windowToolbarStyle(.unifiedCompact)` | Smaller variant — tighter vertical space (like Reminders) |
| `.windowStyle(.hiddenTitleBar)` | Hide the title bar entirely; you draw all chrome (Arc, some games) |

For Codex-style AI apps: `.unified` is right. Sidebar starts below the title bar; content scrolls behind it with material translucency.

### Window restoration

Mac users expect: close the app, reopen it, everything is exactly where they left it. The same windows, the same content, the same scroll positions.

In SwiftUI, use `@SceneStorage` for view-level state and `@AppStorage` for cross-window state:

```swift
struct ContentView: View {
    @SceneStorage("selectedItemID") var selectedItemID: String?
    @SceneStorage("sidebarVisible") var sidebarVisible: Bool = true
    @AppStorage("preferredColorScheme") var preferredColorScheme: ColorSchemePreference = .system

    var body: some View {
        NavigationSplitView(columnVisibility: $sidebarVisible) {
            sidebar
        } content: {
            content
        } detail: {
            detail
        }
    }
}
```

For UIKit/AppKit-based apps: implement `NSWindowRestoration` and `NSUserActivity` for full restoration.

### Minimum window size — math, not guess

The min size is calculated:
```
min width  = sidebar(220) + content(min 400) + detail(min 380) = 1000pt
min height = title bar(28) + content(min 600) + safe-area(12) = 640pt
```

Use `.navigationSplitViewColumnWidth(min: 200, ideal: 220, max: 320)` to constrain each column.

---

## 2. The sidebar pattern

The sidebar is THE Mac convention. Mail, Notes, Reminders, Finder, Music, Photos, Calendar, Linear, Things 3, Granola, Codex — they all use it. If your app shows lists, hierarchy, or navigation, it should have a sidebar.

### `NavigationSplitView` — the SwiftUI primitive

```swift
NavigationSplitView {
    SidebarView(selectedItemID: $selectedItemID)
} content: {
    if let item = selectedItem {
        ItemListView(parentID: item.id)
    } else {
        Text("Select a section")
            .foregroundStyle(.tertiary)
    }
} detail: {
    if let detailItem = selectedDetailID {
        ItemDetailView(itemID: detailItem)
    } else {
        Text("Select an item")
            .foregroundStyle(.tertiary)
    }
}
.navigationSplitViewStyle(.balanced)  // or .prominentDetail
```

### Sidebar dimensions

| Property | Value |
| --- | --- |
| Width | 200–240pt (220 is the sweet spot) |
| Min | 180pt |
| Max | 320pt |
| Row height | 24pt for default, 28pt for content-heavy rows |
| Internal horizontal padding | 8pt (rows extend to sidebar edges) |
| Section header padding | 12pt top, 4pt bottom |

### Sidebar typography

| Element | Font | Weight | Size | Color |
| --- | --- | --- | --- | --- |
| Section header (ALL CAPS) | SF Pro Text | `.semibold` | 11pt | `.secondary`, tracking 1.4 |
| Row label | SF Pro Text | `.regular` | 13pt | `.primary` |
| Selected row label | SF Pro Text | `.semibold` | 13pt | `.white` (on accent bg) |
| Counter badge | SF Pro Text | `.medium` | 11pt | `.secondary` |
| Inactive section badge | SF Pro Text | `.regular` | 11pt | `.tertiary` |

### Sidebar icons

- 16pt SF Symbol, `.symbolRenderingMode(.hierarchical)`
- 8pt gap between icon and label
- Selected row: icon stays the same color (white on accent bg) — DON'T change weight; that causes layout shift

```swift
Label("Inbox", systemImage: "tray.fill")
    .symbolRenderingMode(.hierarchical)
    .font(.system(size: 13))
```

### Selection styling

```swift
List(selection: $selectedID) {
    ForEach(sections) { section in
        Section(section.title.uppercased()) {
            ForEach(section.items) { item in
                NavigationLink(value: item.id) {
                    Label(item.title, systemImage: item.icon)
                }
            }
        }
    }
}
.listStyle(.sidebar)
```

The `.sidebar` list style gives you the proper Mac translucent material background, hover states, and selection coloring. **DO NOT** roll your own list styling for sidebars — `listStyle(.sidebar)` is correct.

### Hover states

```swift
.background(isHovered ? Color(NSColor.controlBackgroundColor).opacity(0.6) : Color.clear)
.onHover { hovering in
    isHovered = hovering
}
```

But `.listStyle(.sidebar)` does this for free — only add custom hover for custom-built lists.

### Type-to-select

A Mac convention that Mac apps rarely implement but ALL good ones do: typing characters jumps to the matching list item. Mail, Finder, and Linear all support this.

For `List`s in SwiftUI, this is automatic on `selection:`-driven lists. For custom lists, implement key handling via `.focusable()` + `.onKeyPress`.

### Drag-to-reorder

```swift
.onMove { from, to in
    items.move(fromOffsets: from, toOffset: to)
}
```

For `List`, this works automatically. For custom sidebars, implement `.draggable` + `.dropDestination`.

### Right-click context menu

Every sidebar row should support right-click for contextual actions:

```swift
.contextMenu {
    Button("Rename", action: rename).keyboardShortcut(.return, modifiers: [])
    Button("Duplicate", action: duplicate).keyboardShortcut("d", modifiers: .command)
    Divider()
    Button("Delete", role: .destructive, action: delete).keyboardShortcut(.delete, modifiers: [])
}
```

### Reference apps

- **Apple Mail**: the canonical sidebar. Mailboxes, smart folders, account hierarchy. Study how disclosure indicators work, how section headers behave, how unread counts align.
- **Linear**: workspaces / teams / projects in sidebar with collapsible sections.
- **Things 3**: areas + projects with elegant nesting.
- **Granola**: meetings list as sidebar, transcripts in content, AI notes in detail.
- **OpenAI Codex**: conversations + project pinned items. Sidebar collapses with `Cmd-Option-S`.

---

## 3. Toolbars

The toolbar is the persistent action surface at the top of your window. In macOS 13+, it integrates with the title bar in `.unified` style — the modern default.

### Item placement

```swift
.toolbar {
    ToolbarItemGroup(placement: .navigation) {
        Button(action: back) { Image(systemName: "chevron.left") }
        Button(action: forward) { Image(systemName: "chevron.right") }
    }

    ToolbarItem(placement: .principal) {
        SearchField()
            .frame(width: 280)
    }

    ToolbarItemGroup(placement: .primaryAction) {
        Button(action: share) { Image(systemName: "square.and.arrow.up") }
        Button(action: settings) { Image(systemName: "gearshape") }
    }
}
```

| Placement | Use |
| --- | --- |
| `.navigation` | Back/forward, source-list buttons |
| `.principal` | Centered title or search field |
| `.primaryAction` | Right-side primary actions (new, share, account) |
| `.automatic` | System-chosen position |
| `.cancellationAction` | Cancel for sheets (top-left in modal context) |
| `.confirmationAction` | OK/Save for sheets (top-right) |

### Icon style

- 16pt SF Symbol at `.semibold` weight
- `.symbolRenderingMode(.hierarchical)` for depth
- 8pt horizontal padding around each item
- 4pt internal padding for clickable area

### Search in the toolbar

A Mac convention: search lives in the toolbar, centered or right-aligned. Use `.searchable(text:)` which automatically places it in the toolbar:

```swift
NavigationSplitView { ... }
    .searchable(text: $query, placement: .toolbar, prompt: "Search messages")
```

This gives you `Cmd-F` to focus the search field for free.

### Hide on scroll (for immersive content)

For content-focused apps (writing, reading, watching), hiding the toolbar on scroll feels great:

```swift
.toolbar(scrollUp ? .visible : .hidden, for: .windowToolbar)
```

Don't overuse — most apps want a persistent toolbar.

---

## 4. The menu bar

A Mac app without a meaningful menu bar feels half-finished. Even single-purpose utilities should have File / Edit / View / Window / Help.

### Standard menus (in order)

1. **App menu** (`AppName`) — auto-populated by system. Adds your app name + standard items.
2. **File** — New, Open, Save, Close, Print, Export
3. **Edit** — Undo, Redo, Cut, Copy, Paste, Select All, Find
4. **View** — Show/Hide Sidebar, Show/Hide Toolbar, Zoom, View Modes
5. **Window** — Minimize, Zoom, Bring All to Front, [list of open windows]
6. **Help** — Search, Help Book

For specific app types, add: **Format** (text editors), **Run** / **Build** (dev tools), **Account** (apps with auth).

### SwiftUI `.commands {}`

```swift
@main
struct CodexApp: App {
    var body: some Scene {
        WindowGroup { ContentView() }
            .commands {
                CommandGroup(replacing: .newItem) {
                    Button("New Conversation") { newConversation() }
                        .keyboardShortcut("n", modifiers: .command)
                    Button("New Project") { newProject() }
                        .keyboardShortcut("n", modifiers: [.command, .shift])
                }
                CommandGroup(after: .sidebar) {
                    Button("Toggle Inspector") { toggleInspector() }
                        .keyboardShortcut("i", modifiers: [.command, .option])
                }
                CommandMenu("Code") {  // Custom menu
                    Button("Run") { run() }.keyboardShortcut("r", modifiers: .command)
                    Button("Stop") { stop() }.keyboardShortcut(".", modifiers: .command)
                }
            }
    }
}
```

### Menu item rules

- **Every item has a keyboard shortcut** unless it's something users will never use frequently (like "About").
- **Use system-standard shortcuts**: `Cmd-N` (new), `Cmd-O` (open), `Cmd-S` (save), `Cmd-W` (close window), `Cmd-Q` (quit), `Cmd-,` (settings).
- **NEVER reassign system shortcuts** to non-standard actions.
- **Capitalize titles**: "New Document" not "new document" — Mac convention is title case.
- **Use ellipsis (`…`)** when a menu item opens a dialog: "Open…", "Save As…", "Find…".
- **Group with dividers**: separators between logical groups.

### Disable items contextually

Menu items should disable when their action is unavailable:

```swift
Button("Save", action: save)
    .keyboardShortcut("s", modifiers: .command)
    .disabled(!hasUnsavedChanges)
```

Disabled items still appear in menus — they just gray out. This communicates "this exists, but isn't applicable right now."

### Reference: Apple Mail's menu bar

Study Apple Mail's full menu hierarchy on your own Mac. Every action you might want is in there. Every item has a shortcut. Every group is separated with dividers. That's the bar.

---

## 5. Keyboard shortcuts & focus

Mac is a keyboard machine. The minimum bar:

| Action | Shortcut | Always? |
| --- | --- | --- |
| New | `⌘N` | Yes |
| New Window | `⌘⇧N` | Multi-window apps |
| Open | `⌘O` | Document apps |
| Save | `⌘S` | Document apps |
| Save As… | `⌘⇧S` | Document apps |
| Close window | `⌘W` | Yes |
| Close tab (if tabbed) | `⌘W` | Tabbed apps |
| Close ALL windows | `⌘⌥W` | Multi-window apps |
| Quit | `⌘Q` | Yes |
| Undo | `⌘Z` | Editable content |
| Redo | `⌘⇧Z` | Editable content |
| Cut / Copy / Paste | `⌘X` / `⌘C` / `⌘V` | Editable content |
| Select All | `⌘A` | Lists, text |
| Find | `⌘F` | Anything searchable |
| Settings | `⌘,` | Yes |
| Toggle sidebar | `⌘⌥S` | Sidebar apps |
| Toggle inspector | `⌘⌥I` | Inspector apps |
| Command palette | `⌘K` | Modern AI/productivity apps |
| Quick switcher | `⌘P` (or `⌘O` if not used for Open) | Project/file apps |
| Toggle full-screen | `⌃⌘F` | Most apps |
| Hide app | `⌘H` | System-provided |
| Hide others | `⌘⌥H` | System-provided |

### `.keyboardShortcut` in SwiftUI

```swift
Button("New", action: createNew)
    .keyboardShortcut("n", modifiers: .command)

Button("Toggle Sidebar", action: toggleSidebar)
    .keyboardShortcut("s", modifiers: [.command, .option])
```

### Focus management

Use `@FocusState` to programmatically focus inputs:

```swift
enum Field: Hashable { case search, content, title }

@FocusState private var focusedField: Field?

TextField("Search", text: $query)
    .focused($focusedField, equals: .search)

// Focus on appear:
.onAppear { focusedField = .search }
```

### Tab navigation

Mac users press `Tab` to move between interactive elements. SwiftUI handles this automatically for `Button`, `TextField`, `Picker`, etc. For custom views, use `.focusable()`.

### Type-to-select

A Mac convention many apps miss. Typing characters with a list focused should jump to the matching item. SwiftUI's `List` does this automatically when bound to `selection:`.

### Number-key shortcuts for tabs/views

For apps with a small number of major views (Tabs 1-9):

```swift
.keyboardShortcut("1", modifiers: .command)  // First tab
.keyboardShortcut("2", modifiers: .command)  // Second tab
// ... etc
```

### Spacebar for primary preview action

Mac users press Space to toggle preview of selected list items (Finder's Quick Look pattern). Replicate for media-list apps.

---

## 6. The command palette (Cmd-K)

The modern AI/productivity Mac app pattern: `⌘K` opens a searchable command palette. Raycast popularized it. Linear, Things 3, Notion, OpenAI Codex, Granola all use it.

### What goes in the palette

- Every menu-bar action (so users can find any action)
- Recently-used items (recent files, conversations, notes)
- Quick filters / searches
- Settings actions
- Help / docs

### SwiftUI implementation pattern

```swift
@State private var paletteVisible = false

ContentView()
    .keyboardShortcut("k", modifiers: .command)
    .sheet(isPresented: $paletteVisible) {
        CommandPaletteView()
            .frame(width: 640, height: 480)
    }
    .background(
        Button("", action: { paletteVisible = true })
            .keyboardShortcut("k", modifiers: .command)
            .hidden()
    )
```

A more elegant approach: use a custom `NSPanel`-based overlay that floats above the window, centered, with a backdrop blur. This is what Raycast and Linear do.

### Palette design

- **Width**: 640pt (most apps)
- **Height**: 480pt with results, ~96pt collapsed (just search field)
- **Background**: thick material backdrop
- **Search field**: 22pt SF Pro Text, regular weight, no border, large placeholder
- **Result rows**: 36pt tall, icon + label + keyboard shortcut hint right-aligned
- **Result groups**: section headers between groups (Files, Actions, Settings)
- **Selected result**: accent color background, white text
- **Arrow keys**: navigate; `Return`: execute; `Esc`: dismiss

### Fuzzy search

Standard fuzzy search algorithm (the same one VS Code, Sublime, Raycast use): characters in order, scoring favors consecutive matches and matches at word starts.

Don't roll your own — use [`fuzzysearch-swift`](https://github.com/sgr-ksmt/FuzzySearch-swift) or similar.

### Reference

OpenAI Codex's `Cmd-K` palette is the modern reference. Linear's `Cmd-K` is the most feature-rich. Raycast IS the palette — study how they make it feel weightless.

---

## 7. Mouse, trackpad & hover states

Mac users move the mouse to discover. Every clickable element should respond to hover.

### `.onHover` in SwiftUI

```swift
@State private var isHovered = false

Button(action: { ... }) { ... }
    .onHover { hovering in
        isHovered = hovering
    }
    .background(isHovered ? Color.gray.opacity(0.1) : Color.clear)
    .animation(.easeOut(duration: 0.12), value: isHovered)
```

### Hover state rules

| Element | Default | Hover |
| --- | --- | --- |
| Sidebar row | clear | `.label.opacity(0.06)` background |
| List row | clear | `.label.opacity(0.04)` background |
| Toolbar icon button | clear | `.label.opacity(0.08)` circular bg |
| Card / tile | regular border | subtle scale (1.01) + slightly darker shadow |
| Inline link | underline none | underline appears |
| Tappable image | none | dim to 0.92 opacity OR show overlay |

**Hover animations** should be FAST (0.10–0.15s `.easeOut`) so they feel responsive. Slow hover transitions feel laggy.

### Cursor changes

Use `NSCursor` for context-appropriate cursors:

```swift
.onHover { hovering in
    if hovering {
        NSCursor.pointingHand.push()
    } else {
        NSCursor.pop()
    }
}
```

Or use the SwiftUI `.pointerStyle()` modifier (macOS 14+):

```swift
.pointerStyle(.link)  // hand cursor
.pointerStyle(.horizontalText)  // I-beam
.pointerStyle(.frameResize(.bottomLeading))  // resize cursor
```

### Trackpad gestures

| Gesture | What it does |
| --- | --- |
| Two-finger scroll | Scroll content |
| Pinch | Zoom (where applicable) |
| Two-finger swipe left/right | Back/forward navigation |
| Three-finger swipe up | Mission Control |
| Three-finger swipe down | App Exposé |
| Four-finger swipe up | Mission Control (default) |
| Force Touch (older trackpads) | Quick Look, deep dictionary lookup |

For navigation back/forward in apps with history:
```swift
.gesture(
    DragGesture(minimumDistance: 50)
        .onEnded { value in
            if value.translation.width > 50 { navigateBack() }
            else if value.translation.width < -50 { navigateForward() }
        }
)
```

### Right-click vs control-click

**Both should work.** Right-click is the modern convention. Control-click is the legacy fallback for users on devices without right-click. SwiftUI's `.contextMenu` handles both automatically.

---

## 8. Context menus (right-click)

Right-click context menus are the Mac equivalent of iOS long-press context menus, but with stricter conventions.

```swift
.contextMenu {
    Button("Open in New Window", action: openInNewWindow)
        .keyboardShortcut("o", modifiers: [.command, .shift])
    Button("Rename", action: rename)
    Button("Duplicate", action: duplicate)
        .keyboardShortcut("d", modifiers: .command)
    Divider()
    Menu("Share") {
        Button("Copy Link", action: copyLink)
        Button("Email", action: emailShare)
    }
    Divider()
    Button("Delete", role: .destructive, action: delete)
        .keyboardShortcut(.delete, modifiers: [])
}
```

### Rules

1. **Mirror menu-bar items** where applicable (Rename, Delete, Duplicate live in both places).
2. **Keyboard shortcuts displayed**: SwiftUI shows the `⌘D` hint automatically next to items that have `.keyboardShortcut`.
3. **Max 8 top-level items**. Use submenus for more.
4. **Destructive items last, with role: .destructive** (renders in red).
5. **Dividers between logical groups**.
6. **No icons in menu items** unless they communicate the action clearly (and even then, sparingly).

### Custom previews

```swift
.contextMenu {
    // menu items
} preview: {
    // SwiftUI preview shown above the menu
    Image(item.thumbnail)
        .resizable()
        .scaledToFit()
        .frame(width: 320, height: 200)
}
```

Finder, Photos, and Mail all do this — long-press on a file shows a preview alongside the menu.

---

## 9. Multi-window & document architecture

Mac apps support multiple windows naturally. This is a fundamental difference from iOS.

### Document-based apps (`NSDocument` / `DocumentGroup`)

```swift
@main
struct MyDocumentApp: App {
    var body: some Scene {
        DocumentGroup(newDocument: MyDocument()) { file in
            DocumentView(document: file.$document)
        }
        .commands {
            // additional commands
        }
    }
}
```

This gives you:
- Auto-Save / Versions
- Drag-from-Finder open
- Recent Documents in File menu
- New Window from File menu
- Each document in its own window

### Non-document apps (`WindowGroup`)

```swift
@main
struct CodexApp: App {
    var body: some Scene {
        WindowGroup("Conversations", id: "main") {
            MainView()
        }

        WindowGroup("Project", for: Project.ID.self) { $projectID in
            ProjectWindow(projectID: projectID)
        }
        .windowResizability(.contentMinSize)

        Settings {
            SettingsView()
        }
    }
}
```

This lets you open additional windows programmatically:

```swift
@Environment(\.openWindow) private var openWindow

Button("Open Project") {
    openWindow(id: "project", value: project.id)
}
```

### Auxiliary windows (Inspector, Library)

For palette/inspector windows that float alongside the main window:

```swift
Window("Inspector", id: "inspector") {
    InspectorView()
}
.windowResizability(.contentSize)
.windowLevel(.floating)  // stays above main window
```

### Tab support

```swift
.windowsToolbarStyleManagedRefactored()  // older API
```

Mac's native window tabbing (the Cmd-T behavior) is automatic for `DocumentGroup`s. Users get tabbed windows for free.

For non-document apps, opt in via Window scene options.

### Window restoration

Apps using `WindowGroup` get restoration of *open windows* automatically. State within each window (scroll position, selection) requires `@SceneStorage` (see §1).

---

## 10. Status bar & MenuBarExtra utilities

For apps that live in the menu bar (Bartender, 1Password mini, Raycast, Karabiner): use `MenuBarExtra`.

```swift
@main
struct UtilityApp: App {
    var body: some Scene {
        MenuBarExtra("My Utility", systemImage: "circle.dashed") {
            MenuBarContent()
        }
        .menuBarExtraStyle(.window)  // or .menu
    }
}
```

| Style | Behavior |
| --- | --- |
| `.menu` | Standard NSMenu — appears as native dropdown menu |
| `.window` | Custom SwiftUI view appears in a popover-style panel |

### Status item icon

- **Always a template image** (`.template`) — system tints it for light/dark menu bar, accessibility settings.
- **22pt × 22pt** size on standard menu bar, 44×44 in Big Sur+ supports larger.
- **No color** — template images are monochrome silhouettes.

Create your icon as a black-on-transparent PNG and tag it as a template:

```swift
NSImage.Name("status-icon")  // Asset Catalog → mark as Template
```

### Show / Hide on click

```swift
@State private var showingPopover = false

MenuBarExtra("My Utility", systemImage: "circle.dashed") {
    PopoverContent()
        .frame(width: 320, height: 480)
}
.menuBarExtraStyle(.window)
```

The system handles opening/closing automatically with `.menuBarExtraStyle(.window)`.

### Apps that demonstrate this well

- **Raycast**: the entire app lives in the menu bar (and via global hotkey)
- **Bartender**: organizes the menu bar itself
- **1Password mini**: quick-access vault from menu bar
- **Cleanshot X**: screenshot tools from menu bar
- **Mosaic / Magnet**: window management from menu bar

---

## 11. Liquid Glass on macOS (Tahoe / macOS 26)

macOS 26 (Tahoe) brought Liquid Glass to the Mac. Sidebars, toolbars, dock, and many system surfaces now use the new translucent material.

### Adopting Liquid Glass

Most Liquid Glass adoption is automatic — using semantic SwiftUI APIs picks it up:

```swift
// Sidebar — Liquid Glass automatic on macOS 26
.listStyle(.sidebar)

// Toolbar — Liquid Glass automatic with .unified style
.windowToolbarStyle(.unified)

// Container background
.background(.bar)              // toolbar-like material
.background(.regularMaterial)  // standard frosted
.background(.thickMaterial)    // more opaque
```

For custom views that want Liquid Glass:

```swift
.glassEffect()                                      // default — regular variant
.glassEffect(.regular.tint(.accentColor))           // tinted glass
.glassEffect(.regular.interactive())                // tap response
.glassEffect(.regular, in: .rect(cornerRadius: 12)) // custom shape
```

### Rules for Liquid Glass on Mac

1. **Glass for floating controls only** — toolbar items, dock-style buttons. Don't make whole content areas glass; users need a solid reading surface.
2. **Tint primary actions sparingly** — see iOS skill for the "tint one thing per screen" rule. Same applies.
3. **Test in both modes** — Liquid Glass renders differently against light backgrounds vs dark vs colorful wallpapers. Check all three.
4. **Reduce Transparency**: when the user has Reduce Transparency on (Accessibility), the system falls back to opaque automatically. You don't need to handle this — but DO test it.
5. **Don't add Liquid Glass to your app icon** — that's the system's job.

### Pre-Tahoe fallback

If your minimum target is macOS 14 or 15:

```swift
@ViewBuilder
func adaptiveBackground() -> some View {
    if #available(macOS 26.0, *) {
        Color.clear.glassEffect(.regular.interactive())
    } else {
        Color.clear.background(.regularMaterial)
    }
}
```

---

## 12. Typography on Mac

Mac typography is mostly the same as iOS (SF Pro family), but with different density expectations.

### System font sizes

Mac uses larger UI font sizes than you might expect:

| Element | Size | Weight |
| --- | --- | --- |
| Window title | 13pt | `.semibold` |
| Sidebar row | 13pt | `.regular` |
| List row primary | 13pt | `.regular` |
| List row secondary | 11pt | `.regular`, secondary color |
| Toolbar button label | 11pt | `.semibold` (rare — usually icon-only) |
| Body text in content | 14pt | `.regular` |
| Reading text in long-form content | 15-17pt | `.regular` |
| Hero numbers / stats | 28-48pt | `.bold` rounded |
| Code (inline + blocks) | 13pt | `.regular` SF Mono |
| Settings label | 13pt | `.regular` |
| Settings help text | 11pt | `.regular`, secondary |

### SF Mono is critical for code apps

```swift
Text(codeSnippet)
    .font(.system(size: 13, design: .monospaced))
```

Or set the font once via `.font(.body.monospaced())`.

For full code editor experiences: ship a custom monospaced font (Berkeley Mono, JetBrains Mono, IBM Plex Mono) bundled with your app. Codex uses a custom monospace font.

### Mac-specific Dynamic Type

Mac doesn't have full iOS-style Dynamic Type, but it does honor "Larger Text" accessibility setting. Use semantic font styles (`.body`, `.callout`, `.caption`) so the system handles scaling.

### Tabular numerals for changing numbers

```swift
Text("\(count)").font(.body.monospacedDigit())
```

For dashboards, timers, prices — same rule as iOS.

---

## 13. Color & material

The color rule from the iOS polish skill applies on Mac too: **pick in OKLCH, ship in Display P3**. Mac displays (especially Pro Display XDR) often have wider gamut than iPhones — P3 colors render even more vividly.

### Semantic Mac colors

| Token | Use |
| --- | --- |
| `Color(.controlBackgroundColor)` | Standard control background |
| `Color(.windowBackgroundColor)` | Window background (regions outside content) |
| `Color(.underPageBackgroundColor)` | Translucent material backings |
| `Color(.textBackgroundColor)` | Text fields, scrollable text |
| `Color(.controlAccentColor)` | User's accent color (System Settings) |
| `Color(.selectedTextBackgroundColor)` | Selected text highlight |
| `Color(.gridColor)` | Subtle list dividers |
| `Color(.separatorColor)` | Standard separators |

### Materials

| Material | Use |
| --- | --- |
| `.ultraThinMaterial` | Very translucent — for floating overlays |
| `.thinMaterial` | Toolbar / sidebar fills |
| `.regularMaterial` | Standard panel backgrounds |
| `.thickMaterial` | More opaque, for sheets |
| `.bar` | Title bar / toolbar material (matches system) |

### Vibrancy

Vibrancy (the color-shifting effect over translucent materials) is automatic when you use semantic colors atop SwiftUI materials. If you hard-code colors over `.regularMaterial`, vibrancy is lost.

### Mac-specific accent handling

Users can change their system accent color in System Settings. Mac apps should respect this via `Color.accentColor`. If your app has a brand color you want to enforce, use a custom named asset that explicitly opts out:

```swift
Color("BrandBlue")  // doesn't follow system accent
```

But for most controls, `.tint(.accentColor)` is correct.

---

## 14. Spacing & density

Mac apps can be denser than iOS apps because users have bigger screens AND precise pointing. But density without rhythm is chaos.

### Mac spacing scale (4pt grid)

| Spacing | Use |
| --- | --- |
| 2pt | Hairlines, sub-row separators |
| 4pt | Tight icon-text gap, intra-row vertical |
| 8pt | Standard small gap, list-item internal padding |
| 12pt | Card internal padding, default vertical rhythm |
| 16pt | Content padding (window content edges) |
| 20pt | Section spacing |
| 24pt | Larger section spacing |
| 32pt | Group separation |
| 48pt+ | Marketing-like screens, hero spacing |

### Content padding

- **Window edges**: 20pt horizontal, 16pt vertical for content areas
- **Sidebar**: rows extend edge-to-edge (no horizontal padding); 8pt vertical between sections
- **Content area between sidebar and detail**: 16pt padding around grouped content
- **Detail pane**: 20pt around content
- **Inspector**: 12pt around content (tighter — it's a side panel)

### Sidebar density

Default `.listStyle(.sidebar)` row heights are 24pt for short rows, 28pt for content-heavy. Don't go below 22pt — accessibility and target size both suffer.

---

## 15. Drag and drop

Mac users expect drag-and-drop *everywhere*: sidebar reordering, dragging files into the app, dragging items out to Finder, dragging between windows.

### `.draggable` and `.dropDestination`

```swift
Text(item.title)
    .draggable(item)  // item conforms to Transferable
```

```swift
.dropDestination(for: Item.self) { items, location in
    for item in items { handleDrop(item) }
    return true
}
```

### `Transferable` conformance

```swift
struct Conversation: Transferable {
    let id: UUID
    let title: String
    let messages: [Message]

    static var transferRepresentation: some TransferRepresentation {
        CodableRepresentation(contentType: .conversation)
        ProxyRepresentation(exporting: \.title)  // for text-based drops
    }
}

extension UTType {
    static var conversation = UTType(exportedAs: "com.heyimjames.codex.conversation")
}
```

### Drag previews

```swift
.draggable(item) {
    // Preview shown during drag
    HStack {
        Image(systemName: "doc")
        Text(item.title)
    }
    .padding(8)
    .background(.regularMaterial, in: .rect(cornerRadius: 8))
}
```

### Drop indicators

For dropping into a list-of-items pane, show a 2pt accent-colored line at the drop position:

```swift
@State private var dropTarget: Int? = nil

.dropDestination(for: Item.self) { items, location in
    // ...
} isTargeted: { targeted in
    dropTarget = targeted ? computeDropIndex(location) : nil
}
```

Render the indicator line between rows at `dropTarget`.

### Drag from Mac to Finder

For apps that produce files (PDFs, images, exports), `.draggable` with a file Transferable representation allows the user to drag the item to Fin

…(truncated)
