Navigation

macOS 13.3+

Organizes a window into a sidebar and a detail view with SwiftUI's native split view.

Split view with a sidebar of projects and the selected project's details
import SwiftUIstruct NavigationUsageExample: View {    struct Project: Identifiable, Hashable {        let id: String        let systemImage: String        let owner: String    }    private let projects = [        Project(id: "Launch plan", systemImage: "checklist", owner: "Ana Souza"),        Project(id: "Build 42", systemImage: "hammer", owner: "Bruno Lima"),        Project(id: "Q3 roadmap", systemImage: "map", owner: "Carla Dias")    ]    @State private var selection: Project.ID? = "Launch plan"    var body: some View {        NavigationSplitView {            List(projects, selection: $selection) { project in                Label(project.id, systemImage: project.systemImage)            }            .navigationTitle("Projects")        } detail: {            if let project = projects.first(where: { $0.id == selection }) {                Form {                    LabeledContent("Owner", value: project.owner)                    LabeledContent("Status", value: "In progress")                }                .formStyle(.grouped)                .navigationTitle(project.id)                .navigationSubtitle(project.owner)            } else {                Text("Select a project")                    .foregroundStyle(.secondary)            }        }    }}

Usage

Mac apps show their main sections in a sidebar. NavigationSplitView brings the Liquid Glass sidebar, its resizing and collapsing, and the window toolbar, so use it directly:

NavigationSplitView {    List(projects, selection: $selection) { project in        Label(project.name, systemImage: project.systemImage)    }    .navigationTitle("Projects")} detail: {    ProjectView(project: selectedProject)}

Bind the list's selection, and the detail shows the selected row. When nothing is selected, show a short message in the detail, as in the example. The title of the detail view appears in the window toolbar, and .navigationSubtitle(_:) adds a line under it.

For a third column, pass a content: view between the sidebar and the detail.

NavigationView is deprecated. Use NavigationSplitView instead, or NavigationStack for a single column.

Keep titles under 15 characters, so they leave room for the toolbar items.

Examples

To go deeper from a view, put it in a NavigationStack. Pushed views get a back button in the toolbar.

Pushed build view with a back button and its title in the toolbar
import SwiftUIstruct NavigationStackExample: View {    struct Build: Identifiable, Hashable {        let id: Int        let status: String    }    private let builds = [        Build(id: 42, status: "Passed"),        Build(id: 41, status: "Failed"),        Build(id: 40, status: "Passed")    ]    @State private var path: [Build] = []    var body: some View {        NavigationStack(path: $path) {            List(builds) { build in                NavigationLink("Build \(build.id)", value: build)            }            .navigationTitle("Builds")            .navigationDestination(for: Build.self) { build in                Form {                    LabeledContent("Status", value: build.status)                    LabeledContent("Branch", value: "main")                }                .formStyle(.grouped)                .navigationTitle("Build \(build.id)")            }        }    }}

NavigationStack(path:) lets you push and go back in code, as on iOS.

Toolbar

To add actions to the window toolbar, use .toolbar(content:) with a plain Button, with no style. Put the toolbar on the detail view, and its items appear on the trailing side of the window, next to the title. Items in the same ToolbarItemGroup share one background.

Window toolbar with share and new project buttons next to the detail title
import SwiftUIstruct NavigationToolbarExample: View {    struct Project: Identifiable, Hashable {        let id: String        let systemImage: String        let owner: String    }    private let projects = [        Project(id: "Launch plan", systemImage: "checklist", owner: "Ana Souza"),        Project(id: "Build 42", systemImage: "hammer", owner: "Bruno Lima"),        Project(id: "Q3 roadmap", systemImage: "map", owner: "Carla Dias")    ]    @State private var selection: Project.ID? = "Launch plan"    var body: some View {        NavigationSplitView {            List(projects, selection: $selection) { project in                Label(project.id, systemImage: project.systemImage)            }            .navigationTitle("Projects")        } detail: {            if let project = projects.first(where: { $0.id == selection }) {                Form {                    LabeledContent("Owner", value: project.owner)                    LabeledContent("Status", value: "In progress")                }                .formStyle(.grouped)                .navigationTitle(project.id)                .toolbar {                    ToolbarItemGroup(placement: .primaryAction) {                        Button("Share", systemImage: "square.and.arrow.up") {}                        Button("New Project", systemImage: "plus") {}                    }                }            } else {                Text("Select a project")                    .foregroundStyle(.secondary)            }        }    }}

Make every toolbar action also available as a command in the menu bar, since people can hide or customize the toolbar. For more on toolbar buttons, see Button. For every placement and toolbar customization, see the Apple documentation.

Color

The toolbar title, the subtitle, and the back button use system colors, so FLX leaves them as they are. The selected row in the sidebar is gray while the sidebar doesn't have keyboard focus. With focus, macOS highlights it with the accent color, which is the app's AccentColor when people keep the system's multicolor accent, so set it to the theme's tint, as in Installation.

For more information, see the Human Interface Guidelines for sidebars and split views, and the Apple documentation.