Back to Provenance

macOS Window & Toolbar Styling Reference

.agents/skills/swiftui-expert-skill/references/macos-window-styling.md

3.3.18.6 KB
Original Source

macOS Window & Toolbar Styling Reference

Window configuration, toolbar styles, sizing, positioning, and navigation patterns specific to macOS SwiftUI apps.

Table of Contents


Quick Lookup Table

APIAvailabilitymacOS-Only?Usage
windowToolbarStyle(_:)macOS 11.0+YesSets toolbar style: .unified, .unifiedCompact, .expanded
windowStyle(_:)macOS 11.0+NoSupports .hiddenTitleBar for chromeless windows
windowResizability(_:)macOS 13.0+NoControls resize handle and green zoom button behavior
defaultSize(width:height:)macOS 13.0+NoInitial frame size when user creates a new window
defaultPosition(_:)macOS 13.0+NoInitial window position on screen
windowIdealPlacement(_:)macOS 15.0+NoClosure with display geometry for precise window positioning
menuBarExtraStyle(_:)macOS 13.0+YesSets MenuBarExtra to .menu or .window style
NavigationSplitViewmacOS 13.0+NoColumns always visible side-by-side on macOS; translucent sidebar
InspectormacOS 14.0+NoTrailing-edge sidebar panel; resizable by dragging

Toolbar Styles

windowToolbarStyle (macOS-only)

Controls how the toolbar and title bar are displayed. Applied to a scene.

swift
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        // Title bar and toolbar in a single row
        .windowToolbarStyle(.unified)
    }
}

Available styles:

StyleDescription
.automaticSystem default
.unifiedTitle bar and toolbar in a single combined row
.unifiedCompactSame as unified but with reduced vertical height
.expandedTitle bar displayed above the toolbar (more toolbar space)
swift
// Unified compact — minimal chrome
.windowToolbarStyle(.unifiedCompact)

// Expanded — title bar above toolbar
.windowToolbarStyle(.expanded)

// Unified with title hidden
.windowToolbarStyle(.unified(showsTitle: false))

Toolbar content

swift
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)
    }
}

Window Style

windowStyle

Set the visual style of a window. Use .hiddenTitleBar for chromeless, immersive windows.

swift
// Standard title bar (default)
WindowGroup {
    ContentView()
}
.windowStyle(.titleBar)

// Hidden title bar — chromeless window
WindowGroup {
    ContentView()
}
.windowStyle(.hiddenTitleBar)

Use case: .hiddenTitleBar is useful for media players, custom-chrome apps, or immersive experiences where the standard title bar is unwanted.


Window Sizing

windowResizability, defaultSize, defaultPosition

These modifiers work together to configure window sizing and placement:

swift
WindowGroup {
    ContentView()
        .frame(minWidth: 600, minHeight: 400)
}
.defaultSize(width: 900, height: 600)
.defaultPosition(.center)
.windowResizability(.contentMinSize)

windowResizability options:

ValueBehavior
.automaticSystem decides resize behavior
.contentSizeFixed to content size; no user resize; zoom button disabled
.contentMinSizeResizable with minimum based on content's minWidth/minHeight

defaultPosition options: .center, .topLeading, .top, .topTrailing, .leading, .trailing, .bottomLeading, .bottom, .bottomTrailing

Guidelines:

  • Set minWidth/minHeight via .frame() on content, enforce with .contentMinSize
  • Use .defaultSize() for initial dimensions (larger than minimums)
  • defaultSize also accepts CGSize

windowIdealPlacement (macOS 15.0+)

For precise programmatic positioning, use a closure with display geometry:

swift
.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.

swift
// 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.

swift
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:).

Inspector (macOS 14.0+)

A trailing-edge panel for supplementary information. On macOS, it appears as a sidebar-style panel that can be resized by dragging its edge.

swift
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")
                    }
                }
            }
    }
}

Commands & Keyboard

Commands, CommandGroup, CommandMenu

Define menu bar commands. On macOS, these populate the menu bar directly. On iOS, they create key commands.

swift
.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.

KeyboardShortcut

On macOS, shortcuts are displayed alongside menu items and in button tooltips on hover.

swift
Button("Save") {
    save()
}
.keyboardShortcut("s", modifiers: .command)

Button("Delete") {
    delete()
}
.keyboardShortcut(.delete, modifiers: .command)

openWindow

Programmatically open a window. If the target window is already open, brings it to the front.

swift
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
        }
    }
}

Best Practices

  • Use .unified or .unifiedCompact for most apps — .expanded only when you need many toolbar items
  • Set min frame sizes on content and use .windowResizability(.contentMinSize) to enforce them
  • Always provide defaultSize so new windows start at a reasonable size
  • Use NavigationSplitView for sidebar navigation — not HSplitView
  • Use Inspector for supplementary panels — it integrates with the toolbar automatically
  • Define Commands for all repeatable actions — users expect keyboard shortcuts on macOS
  • Use #if os(macOS) to wrap macOS-only window configuration in multiplatform projects