`TabView` and Toolbars in SwiftUI: Building App-Wide Navigation
Open any Pixar app — a Toy Box manager, a Monsters Inc. scare tracker, a Ratatouille recipe book — and you’ll find tabs at the bottom. Tabs let users jump between major sections of your app with a single tap, and toolbars give them quick access to actions without leaving the current screen.
You’ll learn how to build a TabView with custom icons, add toolbar buttons with .toolbar, and combine tabs with
navigation stacks. We won’t cover fully custom tab bar designs or advanced ToolbarRole configurations — those belong
in intermediate territory.
What You’ll Learn
- What Is a TabView?
- Creating Tabs
- Controlling the Selected Tab
- Adding Toolbars
- Combining Tabs and Navigation
- Common Mistakes
- What’s Next?
What Is a TabView?
A TabView is a container that organizes your app into multiple sections, each represented by a tab at the bottom of the screen. The user taps a tab to switch between sections.
Apple Docs:
TabView— SwiftUI
Think of it like the departments at Monsters, Inc. — the Scare Floor, the Laugh Floor, and the Admin Office are all in the same building, but you pick which one to visit. Each tab is a department.
Creating Tabs
Each child inside a TabView becomes a separate tab. You give each tab a label using the .tabItem modifier.
struct MonsterHQView: View {
var body: some View {
TabView {
Text("Scare Floor")
.tabItem {
Label("Scares", systemImage: "flame")
}
Text("Laugh Floor")
.tabItem {
Label("Laughs", systemImage: "face.smiling")
}
Text("Admin")
.tabItem {
Label("Admin", systemImage: "gear")
}
}
}
}
Each .tabItem takes a Label with a title and an SF Symbol icon. The tab bar appears at the bottom of the screen, and
tapping a tab switches the visible content.
Tip: Keep your tab count between 2 and 5. More than 5 tabs clutter the bar and make your app harder to navigate. If you need more sections, consider a “More” tab or a different navigation pattern.
Controlling the Selected Tab
By default, the first tab is selected. To control which tab is active programmatically, add a @State property and a
selection binding.
struct PixarHubView: View {
@State private var selectedTab = 0
var body: some View {
TabView(selection: $selectedTab) {
Text("Movies")
.tabItem { Label("Movies", systemImage: "film") }
.tag(0)
Text("Characters")
.tabItem { Label("Characters", systemImage: "person.2") }
.tag(1)
Text("Settings")
.tabItem { Label("Settings", systemImage: "gear") }
.tag(2)
}
}
}
Each tab needs a .tag that matches the type of your selectedTab property. Here we use Int tags, but you can use a
String or a custom enum for more readable code.
You can switch tabs from code by changing selectedTab — for example, a button in the Movies tab could set
selectedTab = 1 to jump to Characters.
Adding Toolbars
A toolbar adds buttons and controls to the navigation bar at the top of the screen, or the bottom bar area. You
create toolbar items using the .toolbar modifier.
Apple Docs:
toolbar(content:)— SwiftUI
struct ToyInventoryView: View {
@State private var toys = ["Woody", "Buzz", "Rex"]
var body: some View {
NavigationStack {
List(toys, id: \.self) { toy in
Text(toy)
}
.navigationTitle("Toy Box")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Add Toy") { }
}
}
}
}
}
ToolbarItem wraps each button and accepts a placement parameter that controls where it appears. Common placements
include:
.primaryAction— trailing edge (top-right on iOS).cancellationAction— leading edge (top-left).bottomBar— bottom toolbar area.navigation— alongside the back button
Multiple Toolbar Items
You can add several items to different placements.
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") { }
}
ToolbarItem(placement: .primaryAction) {
Button("Save") { }
}
}
This creates a “Cancel” button on the left and a “Save” button on the right — a common pattern for modal forms.
Combining Tabs and Navigation
Most real apps put a NavigationStack inside each tab, not around the TabView. This gives each tab its own
independent navigation history.
struct PixarAppView: View {
var body: some View {
TabView {
NavigationStack {
List {
NavigationLink("Toy Story", value: "Toy Story")
NavigationLink("Finding Nemo", value: "Finding Nemo")
}
.navigationTitle("Movies")
.navigationDestination(for: String.self) { movie in
Text("Details for \(movie)")
}
}
.tabItem { Label("Movies", systemImage: "film") }
NavigationStack {
Text("Favorites coming soon!")
.navigationTitle("Favorites")
}
.tabItem { Label("Favorites", systemImage: "star") }
}
}
}
When the user navigates into a movie detail and then switches tabs, the Movies tab remembers its navigation state. Switching back reveals the detail view right where they left it.
Common Mistakes
Wrapping TabView in NavigationStack
Placing NavigationStack outside TabView means all tabs share one navigation stack. Pushing a view in one tab affects
the others.
// ❌ Don't do this — all tabs share one navigation stack
NavigationStack {
TabView {
MovieListView()
.tabItem { Label("Movies", systemImage: "film") }
SettingsView()
.tabItem { Label("Settings", systemImage: "gear") }
}
}
// ✅ Do this — each tab has its own navigation stack
TabView {
NavigationStack { MovieListView() }
.tabItem { Label("Movies", systemImage: "film") }
NavigationStack { SettingsView() }
.tabItem { Label("Settings", systemImage: "gear") }
}
Put the NavigationStack inside each tab so every section manages its own navigation independently.
Forgetting Tags When Using Selection
If you use a selection binding on TabView but forget to add .tag modifiers to each tab, SwiftUI can’t match the
selection value and the binding won’t work.
// ❌ Don't do this — missing tags
TabView(selection: $selectedTab) {
Text("Movies").tabItem { Label("Movies", systemImage: "film") }
Text("Settings").tabItem { Label("Settings", systemImage: "gear") }
}
// ✅ Do this — each tab has a matching tag
TabView(selection: $selectedTab) {
Text("Movies")
.tabItem { Label("Movies", systemImage: "film") }
.tag(0)
Text("Settings")
.tabItem { Label("Settings", systemImage: "gear") }
.tag(1)
}
Every tab must have a .tag whose type matches the selection property.
What’s Next?
TabVieworganizes your app into switchable sections with a tab bar.tabItemprovides the label and icon for each tab- Use a
selectionbinding and.tagto control tabs programmatically .toolbaradds buttons to the navigation bar and bottom bar- Place
NavigationStackinside each tab, not around theTabView
Ready to make your app visually rich? Head over to Images in SwiftUI to learn about SF Symbols, AsyncImage, and custom assets.