Best practices and example-driven guidance for building native macOS SwiftUI scenes and components, including windows, commands, toolbars, settings, split views, inspectors, menu bar extras, and keyboard-driven workflows. Use when creating or refactoring macOS SwiftUI UI, choosing scene types, wiring menus or settings, or needing desktop-specific component patterns and examples.
74
92%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Choose a track based on your goal:
references/components-index.md.appkit-interop skill rather than forcing a shaky workaround.WindowGroup, Window, Settings, MenuBarExtra, or DocumentGroup.MenuBarExtra, use WindowGroup(..., id:) for the primary window when it should appear at launch. Treat Window(...) as a better fit for auxiliary/on-demand singleton windows; in menu-bar-heavy apps, a Window(...) scene may not present the main window automatically at launch.git rev-parse --is-inside-work-tree. If not, run git init at the project root so git-backed editor features unlock from the start. Do not initialize a nested repo inside an existing parent checkout.script/build_and_run.sh so the app has a single kill + build + run entrypoint from the start. Use the exact bootstrap contract from build-run-debug and its references/build-script.md file rather than inventing a second variant here.Color.primary, Color.secondary, semantic foreground styles, .regularMaterial, etc.) so the app follows Light/Dark mode automatically. Do not hardcode white or light backgrounds unless the user explicitly asks for a fixed theme, and do not reach for opaque windowBackgroundColor fills for root panes by default.For any non-trivial macOS app, start with this shape instead of putting the app, all views, models, stores, services, and helpers in one Swift file:
App/<AppName>App.swift: the @main app type and AppDelegate only.Views/ContentView.swift: root layout and high-level composition only.Views/SidebarView.swift, Views/DetailView.swift, Views/ComposerView.swift, etc.: feature views named after their primary type.Models/*.swift: value models, identifiers, and selection enums.Stores/*.swift: persistence and state stores.Services/*.swift: app-server, network, process, or platform clients.Support/*.swift: small formatters, resolvers, extensions, and glue helpers.Keep files small and named after the primary type they contain. If a file starts collecting unrelated views, models, stores, networking clients, and helper extensions, split it before adding more behavior.
Before writing the full UI:
script/build_and_run.sh separate from app source.ContentView.commands, toolbars, sidebars, inspectors, contextual menus, and searchable.MenuBarExtra item titles and action labels short and scannable. Cap visible menu item text at 30 characters; if source content is longer, truncate or summarize it before rendering and open the full content in a dedicated window or detail surface.MenuBarExtra app should still behave like a regular Dock app with a visible main window/process, install an NSApplicationDelegate via @NSApplicationDelegateAdaptor, call NSApp.setActivationPolicy(.regular) during launch, and activate the app with NSApp.activate(ignoringOtherApps: true). If the app is intentionally menu-bar-only, document that .accessory / no-Dock behavior is a deliberate product choice.NavigationSplitView sidebars or root window panes with opaque custom Color(...) or Color(nsColor: .windowBackgroundColor) fills by default. Prefer native macOS sidebar/window materials and system-provided backgrounds unless the user explicitly asks for a custom opaque surface. In sidebar-detail-inspector layouts, let the sidebar keep the standard source-list/material appearance and reserve custom backgrounds for detail or inspector content cards where needed.@SceneStorage for per-window ephemeral state and @AppStorage for durable user preferences.NavigationSplitView or a deliberate manual split layout over iOS-style stacked flows when the app benefits from always-visible structure.List(...).listStyle(.sidebar) and NavigationSplitView sidebars, prefer flat native rows with standard system selection/highlight behavior. Keep rows visually lightweight and Mail-like: at most one leading icon, one strong title line, and one optional secondary detail line in .secondary. Avoid stacked metadata rows, repeated inline utility icons, or dense multi-column status text in the sidebar. Reserve card-style and metadata-heavy surfaces for detail or inspector panes unless the user explicitly asks for a highly custom sidebar treatment.appkit-interop.Prefer a native source-list row shape:
List(selection: $selection) {
ForEach(items) { item in
HStack(spacing: 10) {
Image(systemName: item.systemImage)
.foregroundStyle(.secondary)
.frame(width: 16)
VStack(alignment: .leading, spacing: 2) {
Text(item.title)
.lineLimit(1)
if let detail = item.detail {
Text(detail)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
}
.tag(item.id)
}
}
.listStyle(.sidebar)This keeps selection, highlight, spacing, and scanability aligned with standard macOS sidebars. Keep each row to one icon maximum and one or two text lines maximum, with the second line reserved for a short detail label. Use richer card treatments and denser metadata in the detail or inspector content, not in every sidebar row.
Prefer letting the sidebar and split container use system backgrounds, while applying custom surfaces only to detail cards or inspector sections:
NavigationSplitView {
List(selection: $selection) {
ForEach(items) { item in
Label(item.title, systemImage: item.systemImage)
.tag(item.id)
}
}
.listStyle(.sidebar)
} detail: {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
DetailSummaryCard(item: selectedItem)
DetailMetricsCard(item: selectedItem)
}
.padding()
}
}Avoid painting the sidebar and root split panes with opaque custom fills by default:
NavigationSplitView {
List(items) { item in
SidebarCardRow(item: item)
}
.listStyle(.sidebar)
.background(Color(nsColor: .windowBackgroundColor))
} detail: {
DetailView(item: selectedItem)
.background(Color(.white))
}Use the narrowest state tool that matches the ownership model:
| Scenario | Preferred pattern |
|---|---|
| Local view or control state | @State |
| Child mutates parent-owned value state | @Binding |
| Root-owned reference model on macOS 14+ | @State with an @Observable type |
Child reads or mutates an injected @Observable model | Pass it explicitly as a stored property |
| Window-scoped ephemeral selection or expansion state | @SceneStorage when practical, otherwise scene-owned @State |
| Shared user preference | @AppStorage |
| Shared app service or configuration | @Environment(Type.self) |
| Legacy reference model on older targets | @StateObject at the owner and @ObservedObject when injected |
Choose the ownership location first, then the wrapper. Do not turn simple desktop state into a view model by reflex.
references/components-index.md: entry point for scene and component guidance.references/windowing.md: choosing between WindowGroup, Window, DocumentGroup, and window-opening patterns.references/settings.md: dedicated settings scenes, SettingsLink, and preference layouts.references/commands-menus.md: command menus, keyboard shortcuts, focused values, and desktop action routing.references/split-inspectors.md: sidebars, split views, selection-driven layout, and inspectors.references/menu-bar-extra.md: menu bar extra structure and when it fits.ContentView pretending the whole app is a single screen.@main app, all views, models, stores, networking/process clients, formatters, and extensions. This is acceptable only for tiny throwaway snippets under the new-app threshold above.Window(...) scene and then expecting the main window to appear at launch. Use WindowGroup(..., id:) for the primary launch window and reserve Window(...) for auxiliary/on-demand windows..background(.white), Color.white, or a fixed light palette in a brand-new scaffold without an explicit design requirement..sidebar list, which fights native source-list density, alignment, and selection behavior unless the user explicitly asked for a bespoke visual sidebar.NavigationSplitView sidebars or root window panes with opaque custom color fills by default, instead of letting the sidebar use native source-list/material appearance and reserving custom backgrounds for actual content cards.Use references/components-index.md as the entry point. Each component reference should include:
appkit-interop9d2bf09
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.