Building a Native macOS Preferences Window with SwiftUI

Executive Summary

Native macOS settings require specific window behaviors and toolbar structures to meet user expectations. The introduction of the Settings scene in the macOS 13 SDK (2022) simplified the creation of multi-pane preference windows. This declarative approach replaces the older WindowGroup-plus-command workarounds that previously dominated SwiftUI desktop development.

Once the per-pane views are extracted, a minimal multi-pane settings window reduces to roughly 40 to 60 lines of code. Proper state management ensures user choices reflect immediately across the application.

This guidance targets macOS 13 and later. On macOS 12, the legacy AppKit NSWindow bridge remains necessary, and automatic toolbar generation is unavailable.

The Evolution of Mac Settings

The classic grid-of-icons System Preferences layout held for roughly two decades before the sidebar-driven redesign shipped with macOS 13 Ventura in the fall of 2022. However, the tabbed toolbar pattern for third-party applications predates that shift by years, dating to the icon-toolbar convention Apple standardized in Mac OS X 10.5 (2007).

A developer ships a beautifully branded preferences window with a custom sidebar and gradient toolbar, then discovers users cannot find the setting they expect because the command-comma window looks nothing like any other Mac application's interface. The tabbed-toolbar pattern described here remains the definitive third-party app convention. Apple did not expose the system-level Settings sidebar style as a public SwiftUI API for app preferences.

Summary: The tabbed toolbar is the standard for third-party apps, distinct from the system Settings sidebar.

Anatomy of the Human Interface Guidelines

Apple enforces strict structural expectations for utility windows. The Human Interface Guidelines for Settings recommends each toolbar tab carry a single template symbol plus a one-word label. Two-word labels wrap and break the fixed toolbar rhythm.

Standard content insets for a preferences pane run about 20 points from the window edge, with roughly 8 to 12 point spacing between related control groups.

Reviewing common Mac utility interfaces confirms that symbol choice matters more than it appears. SF Symbols weighted for the toolbar render crisply, but full-color or PNG icons look foreign next to system panes and undercut the trust the window is meant to convey.

Image showing hig_anatomy

Structuring the Tabbed Window Scene

Adding a Settings scene alongside the main WindowGroup automatically wires the command-comma shortcut to open preferences with no extra command declaration. Deciding where to place the tab identity represents the first architectural fork.

Early prototypes using a custom NavigationSplitView failed to meet platform expectations and were discarded. Instead, implementing a TabView automatically generates the native macOS preferences toolbar. Each TabView child needs a .tabItem with a Label. The toolbar picker appears once two or more tabs exist.

If you place a single tab in the TabView, macOS suppresses the toolbar entirely and shows a plain window, so the multi-pane look only materializes at two or more panes. While the Settings scene guarantees automatic toolbar generation, this behavior is limited to standard TabView implementations and does not apply if custom navigation stacks are nested within the scene.

Adaptive Sizing and Layout Constraints

The jarring resize when switching between a short General pane and a taller Advanced pane presents a significant visual defect. Constraining every pane to one shared height often results in awkward empty space.

Under typical conditions, a common native width for preference panes lands between 480 and 620 points. Going wider than about 700 points makes the window read like a document window rather than a utility panel.

Applying .fixedSize(horizontal: false, vertical: true) lets a pane report its natural height so the window animates to fit rather than clipping. Fixed frames fight against localization. A layout tuned to English labels can clip when German or Finnish strings expand, so size to the content rather than hardcoding pixel dimensions where translated text lives.

Note: Hardcoded pixel dimensions will break when translated text expands.

State Persistence with UserDefaults

Image showing adaptive_sizing

The binding strategy splits along data complexity. Toggles, a theme picker, and a font-size slider map cleanly to scalar UserDefaults keys and update every observer instantly. @AppStorage natively supports Bool, Int, Double, String, Data, URL, and any RawRepresentable enum backed by String or Int. Anything else requires a Codable-to-Data bridge.

Because @AppStorage reads and writes the same UserDefaults store the whole app shares, a toggle flipped in the settings window propagates to a menu bar view within the same run loop tick.

The same @AppStorage-bound toggle behaves differently depending on whether the app runs sandboxed. Sandboxed apps write to a container-scoped defaults domain, so a shared suite name is required if a helper or extension must read the same value. UserDefaults is not a place for anything sensitive or large. Storing more than a few kilobytes there, or storing credentials, is a misuse that belongs in the keychain or a real data store.

Quick Tip: Use a shared suite name for @AppStorage if your sandboxed app includes helper extensions.

The Case for Standard Components

Standard Toggle, Picker, and Stepper controls inherit accessibility labels, keyboard focus order, and dark-mode tinting automatically. A custom-drawn equivalent typically requires re-implementing all three by hand.

A preferences window is a low-frequency destination most users visit a handful of times per app install—which is precisely why unfamiliar custom controls cost more than they return. The one defensible exception is a bespoke preview or canvas control unique to the application's function. Even then, wrap it in standard Form and section chrome so the surrounding window stays recognizably native.

Use standard SwiftUI components for settings interfaces. Prioritize platform consistency over brand expression in utility windows for a professional, accessible, and maintainable result.

Comments

No comments so far.

Join the Discussion

Cookie settings