macOS Window & Toolbar Styling Reference
macOS Window & Toolbar Styling Reference
Section titled “macOS Window & Toolbar Styling Reference”Window configuration, toolbar styles, sizing, positioning, and navigation patterns specific to macOS SwiftUI apps.
Table of Contents
Section titled “Table of Contents”- Quick Lookup Table
- Toolbar Styles
- Window Style
- Window Sizing
- MenuBarExtra Style (macOS-only)
- Navigation Layout (macOS behavior)
- Commands & Keyboard
- Best Practices
Quick Lookup Table
Section titled “Quick Lookup Table”| 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 |
Toolbar Styles
Section titled “Toolbar Styles”windowToolbarStyle (macOS-only)
Section titled “windowToolbarStyle (macOS-only)”Controls how the toolbar and title bar are displayed. Applied to a scene.
@mainstruct 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))Toolbar content
Section titled “Toolbar content”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
Section titled “Window Style”windowStyle
Section titled “windowStyle”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 windowWindowGroup { ContentView()}.windowStyle(.hiddenTitleBar)Use case:
.hiddenTitleBaris useful for media players, custom-chrome apps, or immersive experiences where the standard title bar is unwanted.
Window Sizing
Section titled “Window Sizing”windowResizability, defaultSize, defaultPosition
Section titled “windowResizability, defaultSize, defaultPosition”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:
- Set
minWidth/minHeightvia.frame()on content, enforce with.contentMinSize - Use
.defaultSize()for initial dimensions (larger than minimums) defaultSizealso acceptsCGSize
windowIdealPlacement (macOS 15.0+)
Section titled “windowIdealPlacement (macOS 15.0+)”For 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)}MenuBarExtra Style (macOS-only)
Section titled “MenuBarExtra Style (macOS-only)”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 contentMenuBarExtra("Status", systemImage: "chart.bar") { DashboardView()}.menuBarExtraStyle(.window)Navigation Layout (macOS behavior)
Section titled “Navigation Layout (macOS behavior)”NavigationSplitView
Section titled “NavigationSplitView”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:).
Inspector (macOS 14.0+)
Section titled “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.
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
Section titled “Commands & Keyboard”Commands, CommandGroup, CommandMenu
Section titled “Commands, CommandGroup, CommandMenu”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.
KeyboardShortcut
Section titled “KeyboardShortcut”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)openWindow
Section titled “openWindow”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 } }}Best Practices
Section titled “Best Practices”- Use
.unifiedor.unifiedCompactfor most apps —.expandedonly when you need many toolbar items - Set min frame sizes on content and use
.windowResizability(.contentMinSize)to enforce them - Always provide
defaultSizeso new windows start at a reasonable size - Use
NavigationSplitViewfor sidebar navigation — notHSplitView - Use
Inspectorfor supplementary panels — it integrates with the toolbar automatically - Define
Commandsfor all repeatable actions — users expect keyboard shortcuts on macOS - Use
#if os(macOS)to wrap macOS-only window configuration in multiplatform projects