How it works
ZoomNavigationTransition is the transition style that drives the zoom navigation effect, in which a source view appears to expand into the full destination as you push onto a navigation stack and contract back to its origin as you pop. Rather than the standard slide, it visually connects a starting element to its destination so the navigation reads as one continuous, content-aware motion. Reach for it when a tapped element — a card, thumbnail, or photo — should feel like it grows into the screen it presents. You don't instantiate the type directly; you request it through the .zoom navigation transition and pair it with a matched source identity.
Establish a shared identity with @Namespace
The zoom transition needs to match the source and destination across two different views, so it relies on a namespace to tie them together. Declare an @Namespace property — here
namespace— and use that same value on both ends of the navigation so SwiftUI knows which on-screen element is zooming into which destination.Tag the source with matchedTransitionSource(id:in:)
Mark the view the transition should grow out of by applying
.matchedTransitionSource(id: "card", in: namespace). The id ("card") names this particular source, and the namespace scopes it; this is the anchor frame that the zoom expands from and contracts back to. In the example it sits on theNavigationLink's tappable label.Request the style with navigationTransition(.zoom(sourceID:in:))
On the destination content, apply
.navigationTransition(.zoom(sourceID: "card", in: namespace)). The.zoomfactory produces a ZoomNavigationTransition, and itssourceIDandinarguments must match the values you gave the source —"card"andnamespace— so the animation pairs the two views. The destination (Color.orange) becomes the frame the source zooms into.Let it run inside a NavigationStack push
ZoomNavigationTransition applies to a navigation push, so the matched source and the destination's navigationTransition live within a
NavigationStack. When theNavigationLinkactivates, the framework interpolates from the source's frame to the destination's, and reverses it on pop — no per-frame animation code required.
.navigationTransition(.zoom(sourceID: "card", in: namespace)) to a mismatched id like sourceID: "other" and watch the zoom fall back to the default push, confirming that the transition only fires when the source and destination identities agree.Example & preview
Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.
struct ZoomNavigationTransitionDemo: View {
@Namespace private var namespace
var body: some View {
NavigationStack {
NavigationLink {
Color.orange
.ignoresSafeArea()
.navigationTransition(.zoom(sourceID: "card", in: namespace))
} label: {
RoundedRectangle(cornerRadius: 16)
.fill(Color.orange)
.frame(width: 120, height: 120)
.overlay(Text("Open").foregroundStyle(.white))
}
.matchedTransitionSource(id: "card", in: namespace)
.padding()
}
}
}