SwiftUI.cc
Snippets
NavigationLinksBeginner

SwiftUI NavigationLink and navigationDestination

Value-based NavigationLink separates what was tapped from where it goes: links carry values, and navigationDestination(for:) maps each value type to a screen.

4 min readUpdated 2026-06
Typed routing for two models
import SwiftUI

struct Catalog: View {
    var body: some View {
        NavigationStack {
            List {
                NavigationLink("Espresso Beans", value: Product(name: "Espresso Beans"))
                NavigationLink("Acme Roasters", value: Vendor(name: "Acme Roasters"))
            }
            .navigationDestination(for: Product.self) { product in
                Text("Product: \(product.name)")
            }
            .navigationDestination(for: Vendor.self) { vendor in
                Text("Vendor: \(vendor.name)")
            }
            .navigationTitle("Catalog")
        }
    }
}

struct Product: Hashable { let name: String }
struct Vendor: Hashable { let name: String }

NavigationLink routing preview

Older SwiftUI embedded the destination view inside each link, which meant destinations were built eagerly and navigation state was invisible. The value-based pattern splits the roles:

NavigationLink("Show order", value: order.id)      // what

.navigationDestination(for: Order.ID.self) { id in  // where
    OrderDetail(id: id)
}

Tapping appends the value to the stack's path; the destination closure builds the screen lazily. Because history is now just a list of values, programmatic navigation, state restoration, and deep linking all become data manipulation.

Routing by type

Each navigationDestination(for:) handles one type. A list mixing products and vendors routes cleanly with two declarations — no enums required, though a single route enum is a tidy pattern for larger apps.

State-driven pushes

navigationDestination(isPresented: $showDetail) { Detail() } pushes when the binding turns true — the push equivalent of a sheet binding, useful after async work completes.

Common mistakes

  • Declaring navigationDestination inside lazy containers, where it may never register; attach it to the List or stack content instead.
  • Registering the same type twice and shadowing one mapping.
  • Pushing heavyweight model objects as values when an id would keep the path serializable.

Related reference