SwiftUI Navigation API Reference
Overview
SwiftUI's navigation APIs provide data-driven, programmatic navigation that scales from simple stacks to complex multi-column layouts. Introduced in iOS 16 (2022) with NavigationStack and NavigationSplitView, evolved in iOS 18 (2024) with Tab/Sidebar unification, and refined in iOS 26 (2025) with Liquid Glass design.
Evolution timeline
- 2022 (iOS 16) NavigationStack, NavigationSplitView, NavigationPath, value-based NavigationLink
- 2024 (iOS 18) Tab/Sidebar unification, sidebarAdaptable style, zoom navigation transition
- 2025 (iOS 26) Liquid Glass navigation chrome, bottom-aligned search, floating tab bars, backgroundExtensionEffect
Key capabilities
- Data-driven navigation NavigationPath represents stack state, enabling programmatic push/pop and deep linking
- Multi-column layouts NavigationSplitView adapts automatically (3-column on iPad โ single stack on iPhone)
- State restoration Codable NavigationPath + SceneStorage for persistence across app launches
- Tab integration Per-tab NavigationStack with state preservation on tab switch (iOS 18+)
- Liquid Glass Automatic glass navigation bars, sidebars, and toolbars (iOS 26+)
When to use vs UIKit
- SwiftUI navigation New apps, multiplatform, simpler navigation flows โ Use NavigationStack/SplitView
- UINavigationController Complex coordinator patterns, legacy code, specific UIKit features โ Consider UIKit
Related Skills
- Use
axiom-swiftui-nav for anti-patterns, decision trees, pressure scenarios
- Use
axiom-swiftui-nav-diag for systematic troubleshooting of navigation issues
When to Use This Skill
Use this skill when:
- Implementing navigation APIs NavigationStack, NavigationSplitView, NavigationPath, Tab+Navigation
- Deep linking or state restoration URL routing, Codable NavigationPath, SceneStorage
- Adopting iOS 26+ features Liquid Glass navigation, bottom-aligned search, tab bar minimization
- Choosing navigation architecture Stack vs SplitView vs coordinator patterns
API Evolution
Timeline
| Year |
iOS Version |
Key Features |
| 2020 |
iOS 14 |
NavigationView (deprecated iOS 16) |
| 2022 |
iOS 16 |
NavigationStack, NavigationSplitView, NavigationPath, value-based NavigationLink |
| 2024 |
iOS 18 |
Tab/Sidebar unification, sidebarAdaptable, TabSection, zoom transitions |
| 2025 |
iOS 26 |
Liquid Glass navigation, backgroundExtensionEffect, tabBarMinimizeBehavior |
NavigationView (Deprecated)
NavigationView is deprecated as of iOS 16. Use NavigationStack (single-column push/pop) or NavigationSplitView (multi-column) exclusively in new code. Key improvements: single NavigationPath replaces per-link isActive bindings, value-based type safety, built-in Codable state restoration. See "Migrating to new navigation types" documentation.
NavigationStack Complete Reference
NavigationStack represents a push-pop interface like Settings on iPhone or System Settings on macOS.
1.1 Creating NavigationStack
Basic NavigationStack
NavigationStack {
List(Category.allCases) { category in
NavigationLink(category.name, value: category)
}
.navigationTitle("Categories")
.navigationDestination(for: Category.self) { category in
CategoryDetail(category: category)
}
}
With Path Binding (WWDC 2022, 6:05)
struct PushableStack: View {
@State private var path: [Recipe] = []
@StateObject private var dataModel = DataModel()
var body: some View {
NavigationStack(path: $path) {
List(Category.allCases) { category in
Section(category.localizedName) {
ForEach(dataModel.recipes(in: category)) { recipe in
NavigationLink(recipe.name, value: recipe)
}
}
}
.navigationTitle("Categories")
.navigationDestination(for: Recipe.self) { recipe in
RecipeDetail(recipe: recipe)
}
}
.environmentObject(dataModel)
}
}
Path binding + value-presenting NavigationLink + navigationDestination(for:) form the core data-driven navigation pattern.
1.2 NavigationLink (Value-Based)
Value-presenting NavigationLink
NavigationLink(recipe.name, value: recipe)
NavigationLink(value: recipe) {
RecipeTile(recipe: recipe)
}
NavigationLink(recipe.name) {
RecipeDetail(recipe: recipe)
}
How NavigationLink works with NavigationStack
- NavigationStack maintains a
path collection
- Tapping a value-presenting link appends the value to the path
- NavigationStack maps
navigationDestination modifiers over path values
- Views are pushed onto the stack based on destination mappings
1.3 navigationDestination Modifier
Single Type
.navigationDestination(for: Recipe.self) { recipe in
RecipeDetail(recipe: recipe)
}
Multiple Types
NavigationStack(path: $path) {
RootView()
.navigationDestination(for: Recipe.self) { recipe in
RecipeDetail(recipe: recipe)
}
.navigationDestination(for: Category.self) { category in
CategoryList(category: category)
}
.navigationDestination(for: Chef.self) { chef in
ChefProfile(chef: chef)
}
}
Navigation Anti-Patterns
- Never mix
navigationDestination(for:) and NavigationLink(destination:) in the same NavigationStack hierarchy โ causes undefined behavior
- Register
navigationDestination(for:) once per data type โ duplicates cause the wrong view to appear
Placement rules
- Place
navigationDestination outside lazy containers (not inside ForEach)
- Place near related NavigationLinks for code organization
- Must be inside NavigationStack hierarchy
ScrollView {
LazyVGrid(columns: columns) {
ForEach(recipes) { recipe in
NavigationLink(value: recipe) {
RecipeTile(recipe: recipe)
}
}
}
}
.navigationDestination(for: Recipe.self) { recipe in
RecipeDetail(recipe: recipe)
}
ForEach(recipes) { recipe in
NavigationLink(value: recipe) { RecipeTile(recipe: recipe) }
.navigationDestination(for: Recipe.self) { r in
RecipeDetail(recipe: r)
}
}
1.4 NavigationPath
NavigationPath is a type-erased collection for heterogeneous navigation stacks.
Typed Array vs NavigationPath
@State private var path: [Recipe] = []
@State private var path = NavigationPath()
NavigationPath Operations
path.append(recipe)