.agents/skills/swiftui-expert-skill/references/macos-window-styling.md
Window configuration, toolbar styles, sizing, positioning, and navigation patterns specific to macOS SwiftUI apps.
| API | Availability | macOS-Only? | Usage |
|---|---|---|---|
windowToolbarStyle(_:) | macOS 11.0+ | Yes | Sets toolbar style: .unified, .unifiedCompact, .expanded |
windowStyle(_:) | macOS 11.0+ | No | Supports .hiddenTitleBar for chromeless windows |
windowResizability(_:) | macOS 13.0+ | No | Controls resize handle and green zoom button behavior |
defaultSize(width:height:) | macOS 13.0+ | No | Initial frame size when user creates a new window |
defaultPosition(_:) | macOS 13.0+ | No | Initial window position on screen |
windowIdealPlacement(_:) | macOS 15.0+ | No | Closure with display geometry for precise window positioning |
menuBarExtraStyle(_:) | macOS 13.0+ | Yes | Sets MenuBarExtra to .menu or .window style |
NavigationSplitView | macOS 13.0+ | No | Columns always visible side-by-side on macOS; translucent sidebar |
Inspector | macOS 14.0+ | No | Trailing-edge sidebar panel; resizable by dragging |
Controls how the toolbar and title bar are displayed. Applied to a scene.
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
// Title bar and toolbar in a single row
.windowToolbarStyle(.unified)
}
}
Available styles:
| Style | Description |
|---|---|
.automatic | System default |
.unified | Title bar and toolbar in a single combined row |
.unifiedCompact | Same as unified but with reduced vertical height |
.expanded | Title bar displayed above the toolbar (more toolbar space) |
// Unified compact — minimal chrome
.windowToolbarStyle(.unifiedCompact)
// Expanded — title bar above toolbar
.windowToolbarStyle(.expanded)
// Unified with title hidden
.windowToolbarStyle(.unified(showsTitle: false))
struct ContentView: View {
@State private var searchText = ""
var body: some View {
NavigationSplitView {
SidebarView()
} detail: {
DetailView()
}
.toolbar {
ToolbarItem(placement: .automatic) {
Button(action: addItem) {
Label("Add", systemImage: "plus")
}
}
}
.searchable(text: $searchText, placement: .sidebar)
}
}
Set the visual style of a window. Use .hiddenTitleBar for chromeless, immersive windows.
// Standard title bar (default)
WindowGroup {
ContentView()
}
.windowStyle(.titleBar)
// Hidden title bar — chromeless window
WindowGroup {
ContentView()
}
.windowStyle(.hiddenTitleBar)
Use case:
.hiddenTitleBaris useful for media players, custom-chrome apps, or immersive experiences where the standard title bar is unwanted.
These modifiers work together to configure window sizing and placement:
WindowGroup {
ContentView()
.frame(minWidth: 600, minHeight: 400)
}
.defaultSize(width: 900, height: 600)
.defaultPosition(.center)
.windowResizability(.contentMinSize)
windowResizability options:
| Value | Behavior |
|---|---|
.automatic | System decides resize behavior |
.contentSize | Fixed to content size; no user resize; zoom button disabled |
.contentMinSize | Resizable with minimum based on content's minWidth/minHeight |
defaultPosition options: .center, .topLeading, .top, .topTrailing, .leading, .trailing, .bottomLeading, .bottom, .bottomTrailing
Guidelines:
minWidth/minHeight via .frame() on content, enforce with .contentMinSize.defaultSize() for initial dimensions (larger than minimums)defaultSize also accepts CGSizeFor precise programmatic positioning, use a closure with display geometry:
.windowIdealPlacement { context in
let screen = context.defaultDisplay.visibleArea
return WindowPlacement(x: screen.midX, y: screen.midY,
width: screen.width / 2, height: screen.height)
}
Choose between dropdown menu and popover panel for MenuBarExtra.
// Dropdown menu (default)
MenuBarExtra("Status", systemImage: "chart.bar") {
Button("Action") { /* ... */ }
}
.menuBarExtraStyle(.menu)
// Popover panel with custom SwiftUI content
MenuBarExtra("Status", systemImage: "chart.bar") {
DashboardView()
}
.menuBarExtraStyle(.window)
On macOS, NavigationSplitView displays columns side-by-side (never overlaid). The sidebar gets a translucent material background. Columns support variable-width resizing by the user.
NavigationSplitView {
List(items, selection: $selectedId) { item in
Text(item.name)
}
.navigationSplitViewColumnWidth(min: 180, ideal: 220, max: 300)
} detail: {
DetailView(id: selectedId)
}
.navigationSplitViewStyle(.balanced)
Use the three-column variant (sidebar / content / detail) for master-detail-detail layouts. Customize column widths with .navigationSplitViewColumnWidth(min:ideal:max:).
A trailing-edge panel for supplementary information. On macOS, it appears as a sidebar-style panel that can be resized by dragging its edge.
struct ContentView: View {
@State private var showInspector = false
var body: some View {
MainContent()
.inspector(isPresented: $showInspector) {
InspectorView()
.inspectorColumnWidth(min: 200, ideal: 250, max: 400)
}
.toolbar {
ToolbarItem {
Button {
showInspector.toggle()
} label: {
Label("Inspector", systemImage: "info.circle")
}
}
}
}
}
Define menu bar commands. On macOS, these populate the menu bar directly. On iOS, they create key commands.
.commands {
CommandMenu("Tools") {
Button("Run Analysis") { /* ... */ }
.keyboardShortcut("r", modifiers: [.command, .shift])
}
CommandGroup(after: .newItem) {
Button("New From Template...") { /* ... */ }
}
}
CommandGroup placement options: .replacing(_:) replaces a system group, .before(_:) / .after(_:) inserts adjacent to it. Common placements: .newItem, .saveItem, .help, .toolbar, .sidebar.
On macOS, shortcuts are displayed alongside menu items and in button tooltips on hover.
Button("Save") {
save()
}
.keyboardShortcut("s", modifiers: .command)
Button("Delete") {
delete()
}
.keyboardShortcut(.delete, modifiers: .command)
Programmatically open a window. If the target window is already open, brings it to the front.
struct ToolbarActions: View {
@Environment(\.openWindow) private var openWindow
var body: some View {
Button("Connection Doctor") {
openWindow(id: "connection-doctor")
}
Button("Show Message") {
openWindow(value: message.id) // Type-matched to WindowGroup
}
}
}
.unified or .unifiedCompact for most apps — .expanded only when you need many toolbar items.windowResizability(.contentMinSize) to enforce themdefaultSize so new windows start at a reasonable sizeNavigationSplitView for sidebar navigation — not HSplitViewInspector for supplementary panels — it integrates with the toolbar automaticallyCommands for all repeatable actions — users expect keyboard shortcuts on macOS#if os(macOS) to wrap macOS-only window configuration in multiplatform projects