Skip to content

How to use Toolbar in SwiftUI | SwiftUI Bootcamp #63

The navigation bar is prime real estate — it's the first thing users look at to understand where they are and what they can do. The .toolbar modifier gives you full, declarative control over every item in the bar, including placement, and even lets you add a dropdown navigation menu directly to the title.

What You'll Learn

  • How to use .toolbar with ToolbarItem to add items to specific bar positions
  • How ToolbarItemPlacement values like .navigationBarLeading and .navigationBarTrailing work
  • How to add a toolbarTitleMenu for contextual navigation directly in the title area

Mental Model

The navigation bar is like a command bar in a cockpit. There are fixed slots: the left side (leading) and the right side (trailing), plus the center where the title sits. ToolbarItem(placement:) lets you place controls in specific slots. You don't position items with coordinates — you declare which slot each item belongs to and SwiftUI handles the layout for you across all screen sizes and orientations.

The toolbarTitleMenu is like making the cockpit title itself a dropdown — tap the title and a menu appears with additional destinations. This is great for screens where the user might want to jump to a related section quickly.

Detailed Explanation

The .toolbar modifier replaced the deprecated .navigationBarItems(leading:trailing:) modifier. The advantage is flexibility: each ToolbarItem is declared independently with a placement value, and you can have multiple items in the same placement by adding multiple ToolbarItem views inside the .toolbar closure.

Common ToolbarItemPlacement options: .navigationBarLeading (left side of the navigation bar), .navigationBarTrailing (right side), .principal (center, overrides the title), .bottomBar (the toolbar at the bottom of the screen), and .keyboard (above the keyboard). The system positions and sizes items appropriately for each placement.

toolbarTitleMenu is a modifier that adds a chevron indicator to the navigation title and presents a menu when it is tapped. The menu can contain Button views and NavigationLink values, making it useful for switching between major sections of an app without going back to a root screen.

The commented-out modifiers show the evolution of toolbar APIs: .navigationBarHidden(true) was the old way to hide the bar; the new way is .toolbar(.hidden, for: .navigationBar). Similarly, .toolbarBackground(.hidden) hides the bar's background, and .toolbarColorScheme(.dark) forces dark appearance on the bar independent of the app's theme.

Code Structure

ToolbarBootcamp.swift embeds a scrollable list inside a NavigationStack to show the toolbar in context. A leading heart icon and trailing gear icon demonstrate dual toolbar items. The toolbarTitleMenu shows how to build a navigation dropdown from the title. The commented-out code preserves the deprecated API patterns for reference.

Complete Code

ToolbarBootcamp.swift

swift
import SwiftUI

struct ToolbarBootcamp: View {
    
    @State private var text: String = ""
    @State private var paths: [String] = [] // Navigation path for programmatic navigation
    
    var body: some View {
        NavigationStack(path: $paths) {
            ZStack {
                Color.white.ignoresSafeArea()
                
                ScrollView {
                    TextField("Placeholder", text: $text)
                    
                    ForEach(0..<50) { _ in
                        Rectangle()
                            .fill(Color.blue)
                            .frame(width: 200, height: 200)
                    }
                }
            }
            .navigationTitle("Toolbar")
//            .navigationBarItems(
//                leading: Image(systemName: "heart.fill"),
//                trailing: Image(systemName: "gear")
//            )
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) { // Pins to left side of nav bar
                    Image(systemName: "heart.fill")
                }
                ToolbarItem(placement: .navigationBarTrailing) { // Pins to right side of nav bar
//                    HStack {
//                        Image(systemName: "house.fill")
                        Image(systemName: "gear")
//                    }
                }
            }
//            .navigationBarHidden(true)
//            .toolbar(.hidden, for: .navigationBar)       // Modern way to hide the toolbar
//            .toolbarBackground(.hidden, for: .navigationBar) // Hide toolbar background only
//            .toolbarColorScheme(.dark, for: .navigationBar)  // Force dark appearance on toolbar
            .navigationBarTitleDisplayMode(.inline) // Keeps title compact, enabling title menu tap area
            .toolbarTitleMenu { // Adds a dropdown chevron to the navigation title
                Button("Screen 1") {
                    paths.append("Screen 1") // Push Screen 1 via programmatic navigation
                }
                Button("Screen 2") {
                    paths.append("Screen 2") // Push Screen 2 via programmatic navigation
                }
            }
            .navigationDestination(for: String.self) { value in
                Text("New screen: \(value)")
            }
        }
    }
}

struct ToolbarBootcamp_Previews: PreviewProvider {
    static var previews: some View {
        ToolbarBootcamp()
    }
}

Code Walkthrough

  1. @State private var paths: [String] = [] — This array backs the NavigationStack path. The toolbarTitleMenu buttons append to it, demonstrating that toolbar actions can trigger navigation just like any other programmatic navigation.

  2. .toolbar { ... } — The closure accepts ToolbarItem views. This is more flexible than the old navigationBarItems because you can have multiple items per side and you can conditionally include items using if statements inside the closure.

  3. ToolbarItem(placement: .navigationBarLeading) — Declares an item for the left slot. The system respects system buttons (like a back button) and places your item alongside them. On iPad, leading toolbar items may appear in different positions depending on the split view layout.

  4. .navigationBarTitleDisplayMode(.inline) — Displays the title inline (small, in the center) rather than the large title style. The toolbarTitleMenu requires .inline mode to work correctly — the dropdown chevron only appears in inline mode.

  5. .toolbarTitleMenu — The contents of this closure appear in a dropdown when the user taps the title. Buttons inside it can do anything — navigate, change state, show sheets. Here they push new screens onto the navigation path.

  6. .navigationDestination(for: String.self) — Resolves any String value pushed onto paths to a Text view. This is the same pattern from the NavigationStack lesson — toolbar navigation and link navigation both use the same destination registration.

Common Mistakes

Mistake: Using the deprecated .navigationBarItems(leading:trailing:)
This API still compiles but is deprecated since iOS 16. It doesn't support multiple items per side and is not composable. Migrate to .toolbar { ToolbarItem(...) } for all new code.

Mistake: Adding multiple items to the same placement using an HStack
Wrapping two icons in an HStack inside a single ToolbarItem works but gives you no semantic separation between the items. Prefer individual ToolbarItem views per control — this also makes it easier to conditionally show or hide specific items.

Mistake: Expecting toolbarTitleMenu to work with large title display mode
The dropdown chevron only appears when .navigationBarTitleDisplayMode(.inline) is set. With .large or .automatic, the title dropdown is suppressed. Always test your toolbar in the device simulator to catch display mode issues.

Key Takeaways

  • Use .toolbar { ToolbarItem(placement:) } instead of the deprecated .navigationBarItems — it's composable and placement-aware
  • toolbarTitleMenu turns the navigation title into a navigable dropdown, useful for switching contexts without going back to root
  • The navigation toolbar and the bottom toolbar are independent — you can populate both using different ToolbarItemPlacement values in a single .toolbar modifier

Last updated: June 27, 2026

Released under the MIT License.