SwiftUI Navigation with Navigator
When assisting the user with SwiftUI navigation in a project that uses Navigator or the NavigatorUI library, follow the conventions and patterns below. The skill is agent-agnostic; apply it whenever the user mentions Navigator, NavigatorUI, SwiftUI navigation, deep linking, checkpoints, managed navigation stack, or navigation destinations.
When to use this skill
Apply this skill when the user:
- Mentions Navigator, NavigatorUI, or navigation in a Navigator-based codebase.
- Asks about SwiftUI navigation patterns (push, sheet, deep link, return to a screen).
- Works on deep linking, checkpoints, routes, or dismissible behavior.
- Defines or refactors NavigationDestination enums or ManagedNavigationStack usage.
Core conventions
- Destinations: Enums conforming to
NavigationDestination (Hashable + View). Provide a body that returns the correct view for each case; use associated values for parameters. Do not use NavigationLink(value:label:) or NavigationLink(destination:label:) for destination-driven navigation when using Navigator's no-registration flow.
- Links: Use
NavigationLink(to: SomeDestination.case, label: { ... }), or the text shorthand NavigationLink("Title", to: SomeDestination.case) (Navigator 2.1.2+; title may be a String, LocalizedStringKey, or LocalizedStringResource). Navigator wraps the value internally; no per-type navigationDestination(for: MyType.self) registration is required.
- Stack: Use
ManagedNavigationStack { ... } where a NavigationStack would go. It provides the Navigator in the environment and a single internal registration for all NavigationDestination types.
- Navigator access: Use
@Environment(\.navigator) var navigator. Use the navigator for the current stack (from the closure of ManagedNavigationStack or from a child view). Do not assume the environment navigator is the root when inside a tab or presented view—it is the navigator for that stack.
Quick patterns
- Navigate / push:
navigator.navigate(to: HomeDestinations.pageN(55)) or navigator.push(HomeDestinations.pageN(55)); override method with navigator.navigate(to: destination, method: .sheet) (or .managedSheet, .cover, .managedCover, .send).
- Declarative:
.navigate(to: $optionalDestination) or .navigate(trigger: $bool, destination: someDestination).
- Checkpoint return: Define with
NavigationCheckpoints and checkpoint(); attach with .navigationCheckpoint(KnownCheckpoints.home); return with navigator.returnToCheckpoint(KnownCheckpoints.home) or .navigationReturnToCheckpoint(trigger: $flag, checkpoint: KnownCheckpoints.home).
- Deep link / send:
navigator.send(RootTabs.home, HomeDestinations.page2) (or navigator.perform(route: KnownRoutes.profilePhoto)). Receive with .onNavigationReceive { (tab: RootTabs) in ... return .auto } or .onNavigationReceive(assign: $selectedTab); for destinations use .onNavigationReceive { (dest: HomeDestinations, navigator) in navigator.navigate(to: dest); return .auto } or .navigationAutoReceive(HomeDestinations.self).
- Dismiss: From inside a presented view use
navigator.dismiss(). From a parent use dismissPresentedViews(), dismissAnyChildren(), or dismissAny() as appropriate. Custom sheets/covers presented outside navigate(to:) must be wrapped in ManagedPresentationView { content } or content.managedPresentationView() (or ManagedNavigationStack { content } if the sheet has its own stack) so Navigator can manage and dismiss them.
- Lock: Use
.navigationLocked() on a view to make global dismissAny() throw (e.g. during a transaction).
Topic index (reference docs)
For detailed explanations, code samples, and "why" notes, use the reference files:
- Destinations — Defining NavigationDestination enums, body, associated values, delegation to a private View for environment/DI, destination-as-view in sheets, coordination pattern, external provided views.
- Navigation — ManagedNavigationStack, why no registration is needed (AnyNavigationDestination), NavigationLink(to:label), imperative and declarative navigation, NavigationMethod, modular cards/tabs.
- Dismissible — Navigation tree, dismiss vs dismissPresentedViews vs dismissAnyChildren vs dismissAny, navigationLocked, state-driven modifiers, wrapping custom sheets/covers.
- Checkpoints — Defining and establishing checkpoints, returning and returning with value, state-driven return, state restoration, coordinator pattern.
- Deep linking — send(values:), onNavigationReceive and resume types, routes vs destinations, NavigationRouteHandling, perform(route:), one router for URL and in-app.
- Provided destinations — NavigationProvidedDestination, onNavigationProvidedView, NavigationProvidedView and placeholders, modular apps.
Do / don't
- Do: Use
NavigationLink(to: Destination.case); use ManagedNavigationStack; use checkpoints for "return to this place"; use send/receive for deep linking and tab switching; wrap custom presented views in ManagedPresentationView or .managedPresentationView() when Navigator should manage them.
- Don't: Use
NavigationLink(destination:label:); use NavigationLink(value:label:) for destination types when aiming for no-registration flow (unless you explicitly register); present sheets or covers without wrapping when Navigator needs to dismiss or deep-link into the app.
Root setup
Create a root Navigator(configuration: NavigationConfiguration(...)) and apply .navigationRoot(navigator) at the root view. Register provided destinations and receive handlers (e.g. .onNavigationProvidedView(...), .onNavigationRoute(router), .onNavigationReceive(...)) above .navigationRoot(navigator) as needed.
References
- In-repo: README, Documentation.docc, NavigatorDemo app for patterns.
- External: Navigator Documentation; Medium articles — Advanced Navigation Destinations, Eliminating Navigation Registrations, Advanced Deep Linking, Navigation Checkpoints, SwiftUI Navigation With Dismissible.
1---2name: swiftui-navigation-navigator3description: Guides SwiftUI navigation using the Navigator/NavigatorUI library—NavigationDestination enums, ManagedNavigationStack, NavigationLink(to:label), deep linking (send/onNavigationReceive), checkpoints, dismissible views, and modular/provided destinations. Use when implementing or discussing SwiftUI navigation with Navigator, deep linking, checkpoints, or NavigatorUI.4---56# SwiftUI Navigation with Navigator78When assisting the user with SwiftUI navigation in a project that uses Navigator or the NavigatorUI library, follow the conventions and patterns below. The skill is agent-agnostic; apply it whenever the user mentions Navigator, NavigatorUI, SwiftUI navigation, deep linking, checkpoints, managed navigation stack, or navigation destinations.910## When to use this skill1112Apply this skill when the user:1314- Mentions **Navigator**, **NavigatorUI**, or navigation in a Navigator-based codebase.15- Asks about **SwiftUI navigation** patterns (push, sheet, deep link, return to a screen).16- Works on **deep linking**, **checkpoints**, **routes**, or **dismissible** behavior.17- Defines or refactors **NavigationDestination** enums or **ManagedNavigationStack** usage.1819## Core conventions2021- **Destinations**: Enums conforming to `NavigationDestination` (Hashable + View). Provide a `body` that returns the correct view for each case; use associated values for parameters. Do not use `NavigationLink(value:label:)` or `NavigationLink(destination:label:)` for destination-driven navigation when using Navigator's no-registration flow.22- **Links**: Use **`NavigationLink(to: SomeDestination.case, label: { ... })`**, or the text shorthand **`NavigationLink("Title", to: SomeDestination.case)`** (Navigator 2.1.2+; title may be a `String`, `LocalizedStringKey`, or `LocalizedStringResource`). Navigator wraps the value internally; no per-type `navigationDestination(for: MyType.self)` registration is required.23- **Stack**: Use **`ManagedNavigationStack { ... }`** where a `NavigationStack` would go. It provides the `Navigator` in the environment and a single internal registration for all `NavigationDestination` types.24- **Navigator access**: Use **`@Environment(\.navigator) var navigator`**. Use the navigator for the *current* stack (from the closure of `ManagedNavigationStack` or from a child view). Do not assume the environment navigator is the root when inside a tab or presented view—it is the navigator for that stack.2526## Quick patterns2728- **Navigate / push**: `navigator.navigate(to: HomeDestinations.pageN(55))` or `navigator.push(HomeDestinations.pageN(55))`; override method with `navigator.navigate(to: destination, method: .sheet)` (or `.managedSheet`, `.cover`, `.managedCover`, `.send`).29- **Declarative**: `.navigate(to: $optionalDestination)` or `.navigate(trigger: $bool, destination: someDestination)`.30- **Checkpoint return**: Define with `NavigationCheckpoints` and `checkpoint()`; attach with `.navigationCheckpoint(KnownCheckpoints.home)`; return with `navigator.returnToCheckpoint(KnownCheckpoints.home)` or `.navigationReturnToCheckpoint(trigger: $flag, checkpoint: KnownCheckpoints.home)`.31- **Deep link / send**: `navigator.send(RootTabs.home, HomeDestinations.page2)` (or `navigator.perform(route: KnownRoutes.profilePhoto)`). Receive with `.onNavigationReceive { (tab: RootTabs) in ... return .auto }` or `.onNavigationReceive(assign: $selectedTab)`; for destinations use `.onNavigationReceive { (dest: HomeDestinations, navigator) in navigator.navigate(to: dest); return .auto }` or `.navigationAutoReceive(HomeDestinations.self)`.32- **Dismiss**: From inside a presented view use `navigator.dismiss()`. From a parent use `dismissPresentedViews()`, `dismissAnyChildren()`, or `dismissAny()` as appropriate. Custom sheets/covers presented outside `navigate(to:)` must be wrapped in **`ManagedPresentationView { content }`** or **`content.managedPresentationView()`** (or **`ManagedNavigationStack { content }`** if the sheet has its own stack) so Navigator can manage and dismiss them.33- **Lock**: Use **`.navigationLocked()`** on a view to make global `dismissAny()` throw (e.g. during a transaction).3435## Topic index (reference docs)3637For detailed explanations, code samples, and "why" notes, use the reference files:3839- **[Destinations](reference/Destinations.md)** — Defining NavigationDestination enums, body, associated values, delegation to a private View for environment/DI, destination-as-view in sheets, coordination pattern, external provided views.40- **[Navigation](reference/Navigation.md)** — ManagedNavigationStack, why no registration is needed (AnyNavigationDestination), NavigationLink(to:label), imperative and declarative navigation, NavigationMethod, modular cards/tabs.41- **[Dismissible](reference/Dismissible.md)** — Navigation tree, dismiss vs dismissPresentedViews vs dismissAnyChildren vs dismissAny, navigationLocked, state-driven modifiers, wrapping custom sheets/covers.42- **[Checkpoints](reference/Checkpoints.md)** — Defining and establishing checkpoints, returning and returning with value, state-driven return, state restoration, coordinator pattern.43- **[Deep linking](reference/DeepLinking.md)** — send(values:), onNavigationReceive and resume types, routes vs destinations, NavigationRouteHandling, perform(route:), one router for URL and in-app.44- **[Provided destinations](reference/ProvidedDestinations.md)** — NavigationProvidedDestination, onNavigationProvidedView, NavigationProvidedView and placeholders, modular apps.4546## Do / don't4748- **Do**: Use `NavigationLink(to: Destination.case)`; use `ManagedNavigationStack`; use checkpoints for "return to this place"; use send/receive for deep linking and tab switching; wrap custom presented views in `ManagedPresentationView` or `.managedPresentationView()` when Navigator should manage them.49- **Don't**: Use `NavigationLink(destination:label:)`; use `NavigationLink(value:label:)` for destination types when aiming for no-registration flow (unless you explicitly register); present sheets or covers without wrapping when Navigator needs to dismiss or deep-link into the app.5051## Root setup5253Create a root `Navigator(configuration: NavigationConfiguration(...))` and apply **`.navigationRoot(navigator)`** at the root view. Register provided destinations and receive handlers (e.g. `.onNavigationProvidedView(...)`, `.onNavigationRoute(router)`, `.onNavigationReceive(...)`) above `.navigationRoot(navigator)` as needed.5455## References5657- In-repo: README, Documentation.docc, NavigatorDemo app for patterns.58- External: [Navigator Documentation](https://hmlongco.github.io/Navigator/documentation/navigatorui/); Medium articles — Advanced Navigation Destinations, Eliminating Navigation Registrations, Advanced Deep Linking, Navigation Checkpoints, SwiftUI Navigation With Dismissible.