macos-settings-ui
Build a proper macOS settings/preferences window with liquid glass support for macOS 26 (Tahoe). Use this skill whenever the user asks to create a settings window, preferences UI, settings view, or preferences pane for a macOS app. Also trigger when the user mentions "liquid glass settings", "NavigationSplitView settings", "grouped Form settings", "macOS settings layout", or wants to add/fix/rebuild a settings screen in any native macOS SwiftUI app. This includes phrases like "add settings to my app", "create a preferences window", "my settings look wrong", "fix my settings UI", or even just "I need a settings view". If the user is building a macOS app and mentions settings or preferences in any context, use this skill.
Savant verdict: Pending analysis
Imported from the source hub; Savant's analysis is still running.
Live evaluation
Not evaluated yet. Workspaces can request a live evaluation.
Security assessment
Scan pending.
Structure
- 3 referenced files aren't in the packagereferences/SettingsWindowController.swift, references/SettingsView.swift, references/ExampleDetailPane.swift. They may be binary, too large to catalog, or missing upstream.
- No license declaredConfirm you may reuse this skill before importing it into your repository.
SKILL.md
---
name: macos-settings-ui
description: >
Build a proper macOS settings/preferences window with liquid glass support for macOS 26 (Tahoe).
Use this skill whenever the user asks to create a settings window, preferences UI, settings view,
or preferences pane for a macOS app. Also trigger when the user mentions "liquid glass settings",
"NavigationSplitView settings", "grouped Form settings", "macOS settings layout", or wants to
add/fix/rebuild a settings screen in any native macOS SwiftUI app. This includes phrases like
"add settings to my app", "create a preferences window", "my settings look wrong", "fix my
settings UI", or even just "I need a settings view". If the user is building a macOS app and
mentions settings or preferences in any context, use this skill.
---
# macOS Settings UI with Liquid Glass
This skill produces a native macOS settings window that follows Apple's macOS 26 design guidelines. The result is a sidebar + detail NavigationSplitView with liquid glass window chrome, back/forward toolbar navigation, grouped Form sections with transparent backgrounds, and proper scroll edge effects.
## Architecture Overview
The settings UI is composed of 3 layers:
1. **Window Controller** (`SettingsWindowController.swift`) — An `NSWindowController` that creates the `NSWindow` programmatically with `.fullSizeContentView`. This is what gives the window rounded liquid glass corners. You cannot get this effect from a SwiftUI `Window` scene.
2. **Root View** (`SettingsView.swift`) — A `NavigationSplitView` with a sidebar list and detail pane. Includes back/forward navigation history in the toolbar, which also forces the creation of an `NSToolbar` (required for the liquid glass title bar treatment).
3. **Detail Panes** (one file per tab) — Each pane uses `Form { Section(...) { ... } }.formStyle(.grouped).scrollContentBackground(.hidden)`.
## Why NSWindowController Instead of SwiftUI Window Scene
SwiftUI's declarative `Window` scene does not expose the `NSWindow` style mask. The `.fullSizeContentView` flag must be set at window creation time for macOS 26 to render the liquid glass chrome (rounded corners, translucent sidebar, blurred title bar). Trying to inject it later via `NSViewRepresentable` is unreliable because SwiftUI resets the window's configuration.
The `NSWindowController` approach also lets you control the toolbar style, frame autosave, minimum size, and delegate lifecycle directly.
## Critical Modifiers
Every detail pane MUST have these three modifiers on its `Form`:
```swift
.formStyle(.grouped)
.scrollContentBackground(.hidden)
.contentMargins(.top, 8, for: .scrollContent)
```
- `.formStyle(.grouped)` — gives the native inset rounded-rect section appearance
- `.scrollContentBackground(.hidden)` — makes the form background transparent so the liquid glass window chrome shows through. Without this, you get an opaque white/dark background that breaks the glass effect.
- `.contentMargins(.top, 8)` — adds breathing room between the toolbar and the first section
The sidebar List MUST have:
```swift
.listStyle(.sidebar)
.scrollEdgeEffectStyleSoftIfAvailable() // macOS 26 progressive blur at scroll edges
.navigationTitle("Settings")
```
The NavigationSplitView MUST have:
```swift
NavigationSplitView(columnVisibility: .constant(.all)) // sidebar always visible
// ...
.navigationTitle("Settings")
.navigationSplitViewStyle(.balanced)
```
## Reference Implementation
Read the reference files for complete, working code:
- `references/SettingsWindowController.swift` — The window controller (copy as-is, adapt the activation policy calls to your app)
- `references/SettingsView.swift` — The root view with sidebar, detail routing, and navigation history
- `references/ExampleDetailPane.swift` — A template detail pane showing common control patterns (Toggle, Picker, Slider, LabeledContent)
## Step-by-Step: Adding Settings to a New App
### 1. Create the Tab Enum
Define your settings categories. Each case needs a title and SF Symbol icon:
```swift
enum SettingsTab: String, CaseIterable, Identifiable {
case general
case appearance
case about
var id: Self { self }
var title: String {
switch self {
case .general: "General"
case .appearance: "Appearance"
case .about: "About"
}
}
var systemImage: String {
switch self {
case .general: "gearshape"
case .appearance: "paintbrush"
case .about: "info.circle"
}
}
}
```
### 2. Create the Window Controller
Copy `references/SettingsWindowController.swift` into your project. Adapt:
- The initial `contentRect` size (default `700x540` is good for most apps)
- The `minSize` (default `620x460`)
- The activation policy calls (`AppActivationPolicy.enter()/leave()`) — if your app isn't a menu-bar-only app, remove these
### 3. Create the Root Settings View
Copy `references/SettingsView.swift`. Adapt:
- The `SettingsTab` enum cases to match your categories
- The `SettingsDetailView` switch to return your panes
### 4. Create Detail Panes
For each tab, create a pane file. Use this template:
```swift
import SwiftUI
struct GeneralSettingsPane: View {
@AppStorage("someKey") private var someValue = false
var body: some View {
Form {
Section("Section Name") {
Toggle("Toggle label", isOn: $someValue)
}
}
.formStyle(.grouped)
.scrollContentBackground(.hidden)
.contentMargins(.top, 8, for: .scrollContent)
}
}
```
### 5. Open Settings from Your App
From a menu bar, button, or anywhere:
```swift
SettingsWindowController.show(tab: .general)
```
Do NOT use a SwiftUI `Window` scene. Do NOT use `openWindow(id:)`. The window controller handles everything.
### 6. Remove Any SwiftUI Window Scene
If you previously had a `Window("Settings", id: "SETTINGS")` scene in your `App` struct, remove it entirely. The `SettingsWindowController` replaces it.
## Common Control Patterns Inside Form Sections
**Toggle with description:**
```swift
Toggle(isOn: $value) {
VStack(alignment: .leading, spacing: 2) {
Text("Primary label")
Text("Description text explaining what this does.")
.font(.caption)
.foregroundStyle(.secondary)
}
}
.toggleStyle(.switch)
```
**Picker (dropdown):**
```swift
Picker("Label", selection: $value) {
Text("Option A").tag(OptionEnum.a)
Text("Option B").tag(OptionEnum.b)
}
.pickerStyle(.menu)
```
**Picker (segmented):**
```swift
Picker("Label", selection: $value) {
ForEach(SomeEnum.allCases) { item in
Text(item.title).tag(item)
}
}
.pickerStyle(.segmented)
```
**Slider with value label:**
```swift
LabeledContent("Size") {
HStack(spacing: 12) {
Slider(value: $size, in: 24...96, step: 2)
.frame(width: 180)
Text("\(Int(size)) pt")
.monospacedDigit()
.foregroundStyle(.secondary)
.frame(width: 46, alignment: .trailing)
}
}
```
**Button row:**
```swift
HStack(spacing: 8) {
Button("Action") { doSomething() }
.controlSize(.small)
Button("Reset") { reset() }
.controlSize(.small)
.disabled(isDefault)
}
```
## Menu-Bar-Only Apps
If your app uses `NSApp.setActivationPolicy(.accessory)` (no Dock icon), you need activation policy management so the settings window brings the app to the foreground. The reference `SettingsWindowController` calls `AppActivationPolicy.enter()` on show and `.leave()` on close. Implement this as a simple reference-counting wrapper:
```swift
@MainActor
enum AppActivationPolicy {
private static var count = 0
static func enter() {
count += 1
NSApp.setActivationPolicy(.regular)
NSApp.activate(ignoringOtherApps: true)
}
static func leave() {
count = max(0, count - 1)
guard count == 0 else { return }
Task { @MainActor in
NSApp.setActivationPolicy(.accessory)
}
}
}
```
If your app always shows in the Dock, remove the `AppActivationPolicy` calls from the window controller and just use `NSApp.activate(ignoringOtherApps: true)` in `showWindow`.
## macOS Version Compatibility
The `scrollEdgeEffectStyle(.soft)` API is macOS 26+ only. Always wrap it in an availability check:
```swift
private extension View {
@ViewBuilder
func scrollEdgeEffectStyleSoftIfAvailable() -> some View {
if #available(macOS 26.0, *) {
scrollEdgeEffectStyle(.soft, for: .all)
} else {
self
}
}
}
```
The rest of the pattern (NSWindowController with `.fullSizeContentView`, grouped Form, NavigationSplitView) works on macOS 14+.