swiftui-mac-pro-skill — Production Mac SwiftUI for Claude Code
Currently focused on macOS 15 Sequoia (baseline) + macOS 26 Tahoe (Liquid Glass opt-in). These are the most-deployed macOS versions in 2026. Future macOS releases will be added as Apple ships them. The skill is version-flexible: APIs gated by
if #available(...)keep older targets working.
When this skill applies
Use these instructions for SwiftUI work whose deployment target is macOS 15 Sequoia. The skill is macOS 15-first: every code path must run on macOS 15. macOS 26 (Tahoe) Liquid Glass features are allowed only as opt-in enhancements gated with if #available(macOS 26.0, *) — see references/macos26-optin.md.
Code must compile cleanly under Swift 5 and Swift 6 language modes (including the Swift 6.2 default-isolation defaults if the project enables them). New code should not rely on patterns that become errors when strict concurrency is on. See references/swift6-concurrency.md.
Distribution-aware: Every Mac project ships to one of: Mac App Store (App Sandbox required), Direct distribution via Developer ID (Hardened Runtime required, Sandbox optional), or both. Before introducing any API that touches the file system, network, devices, or other apps, check the project's .entitlements file. If unstated, assume both modes must work — that's the most restrictive path.
Respect the existing project. Before changing code, check the deployment target, Swift language mode, persistence stack, sandbox status, and naming conventions. Existing settings win unless the user explicitly asks to migrate. Never introduce macOS 16+ / macOS 26 APIs without an availability gate.
Working rules
- Match the app's existing architecture, deployment target, and naming before introducing a new pattern.
- Distribution-aware: never assume sandbox on/off — check
.entitlementsfirst. If not stated, assume both Mac App Store + Direct must work. - Prefer Apple-built frameworks. Fall to AppKit when SwiftUI cannot deliver pro-grade UX (NSToolbar customization, NSWindowController state, custom titlebar, NSStatusItem behavior).
- Write complete, compilable snippets — include imports, no undefined placeholders inside "complete" examples.
- Build or typecheck when a local project is available before declaring code done.
- Never introduce macOS 16+ / macOS 26 APIs without
if #available(macOS 26.0, *)gate. - Swift 6.2 default-isolation aware: explicit
@MainActoron view models even when project setsdefaultActorIsolation = MainActor(consistency + readability).
Deprecated patterns to avoid
These are deprecated as of macOS 15 SwiftUI. Each has a recommended replacement and a brief reason — knowing why helps when you encounter edge cases.
Navigation
NavigationView→NavigationSplitView(sidebar/detail) orNavigationStack(push).NavigationViewproduces unpredictable layout on macOS and is being removed.NavigationLink(isActive:),tag:,selection:→List(selection:)+.navigationDestination(for:)with typed values. Old initializers don't compose with typed paths and break programmatic back-navigation.
State / Observation
ObservableObject/@Published/@ObservedObject/@StateObject→@Observable+@State/@Bindable. Observation tracks reads at the property level;@Publishedinvalidates on any change.@StateObjectfor an@Observableclass →@State. CRITICAL: the migration is NOT 1:1 —@StateObjectwas lazy/once-per-lifetime;@Stateinitializes eagerly. If your view model has a heavy initializer (network call, file I/O, model load), the regression can cause double-fetches or re-init storms. Refactor the heavy work into a.taskbody or move it behind a lazy property.ReferenceFileDocumentstill requiresObservableObjectconformance (not yet@Observable-native, verified 2026-05-06). Keep@Publishedproperties for that protocol only.
Previews
PreviewProvider→#Preview.
Alerts and dialogs
Alert/ActionSheetstructs →.alert(...)/.confirmationDialog(...)modifiers.
Lifecycle
.onChange(of:perform:)(single-parameter closure) → zero- or two-parameter overload ({ }or{ old, new in }).Task { … }inside.onAppearfor initial async load →.task(or.task(id:))..taskcancels automatically on disappear.
Styling and gestures
.animation(_:)(broad view-level) →.animation(_:value:)orwithAnimation { … }..accentColor(_:)→ asset catalog accent or.tint(_:).Text(...).onTapGesturefor tappable controls →Button.
Tabs
.tabItem→Tab(macOS 15+) for new code..tabItemis soft-deprecated in macOS 15 but not yet hard-removed.- Nuance for
Settingsscene specifically: both.tabItemandTabrender the same underlyingNSTabView; Apple's own samples (e.g., Backyard Birds, WWDC24) still use.tabItemfor Settings. Migration is code cleanliness, not API removal urgency for that case.
AppKit lifecycle
NSApp.activate(ignoringOtherApps: true)→NSApp.activate()(macOS 14+). Old form deprecated; emits runtime warning on macOS 15 because it ignores activation policy. New form respects the policy and is the only correct call going forward.UserDefaults.standard.synchronize()→ remove the call entirely. No-op since macOS 10.12 (2016). Apple docs: "This method is unnecessary and shouldn't be used."NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)→@Environment(\.openSettings)+openSettings()(macOS 14+). The selector form is private API and brittle; the environment value is the official, type-safe replacement.
Window
.background(Color)for window background →.containerBackground(_:for: .window)(macOS 14+).NSSavePanel/NSOpenPaneldirect presentation →.fileImporter(_:)/.fileExporter(_:)SwiftUI modifiers (macOS 11+) where possible.
macOS 26 features — opt-in via availability gates
Liquid Glass APIs (glassEffect, Glass, GlassEffectContainer, .buttonStyle(.glass), Observations, tabBarMinimizeBehavior) crash on macOS 15. Always gate:
if #available(macOS 26.0, *) {
SomeView().glassEffect(.regular)
} else {
SomeView()
}
Liquid Glass auto-adopts on toolbars/sidebars/sheets when the app is recompiled with Xcode 26 — no code change needed for system chrome. See references/macos26-optin.md.
Window scenes
The biggest cognitive shift from iOS to Mac SwiftUI is the Scene system. Every Mac app has one or more Scenes that define windows. Pick the right one:
| Scene | Use when | macOS available |
|---|---|---|
WindowGroup | Multi-instance content; user expects File > New Window | 11+ |
WindowGroup(for: T.self) | One window per data value (open Document X brings existing to front if open) | 13+ |
Window | Single uniquely-identified window (Inspector, Welcome, About) | 13+ |
Settings | App preferences — auto-wires Settings… menu and Cmd+, | 11+ |
MenuBarExtra | Status-bar utility | 13+ |
DocumentGroup | File-backed documents with Open/Save/Recent | 11+ |
Minimal example combining the most common scenes:
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.windowToolbarStyle(.unified)
Window("Inspector", id: "inspector") {
InspectorView()
}
.windowLevel(.floating)
Settings {
SettingsView()
}
MenuBarExtra("Status", systemImage: "star") {
StatusMenu()
}
.menuBarExtraStyle(.menu)
}
}
For multi-window patterns (@FocusedValue, \.openWindow, .handlesExternalEvents, state restoration), see [references/windows-and-sce