home / skills / pproenca / dot-skills / ios-navigation

ios-navigation skill

/skills/.experimental/ios-navigation

This skill enforces iOS 17+ SwiftUI navigation best practices using Airbnb patterns to ensure clean architecture and safe transitions.

npx playbooks add skill pproenca/dot-skills --skill ios-navigation

Review the files below or copy the command above to add this skill to your agents.

Files (58)
SKILL.md
9.7 KB
---
name: ios-navigation
description: Opinionated SwiftUI navigation enforcement for iOS 17+ apps using Airbnb's Clean MVVM + Coordinator architecture. Enforces @Equatable views, @Observable-only state, coordinator-owned NavigationStack and modals, zoom transitions, and strict anti-patterns. This skill should be used when designing navigation hierarchies, implementing screen transitions, choosing between sheet and push, orchestrating multi-step flows, building coordinator patterns with @Observable/@Environment/@Bindable, or reviewing navigation code for anti-patterns and Airbnb architecture compliance.
---

# Airbnb iOS Navigation Best Practices

Opinionated, strict navigation enforcement for SwiftUI iOS 17+ apps. Contains 54 rules across 8 categories derived from Airbnb Engineering, Apple WWDC sessions, and Clean Architecture patterns. Mandates @Equatable views, @Observable-only state, coordinator-owned navigation, and zero legacy APIs.

## Non-Negotiable Constraints (iOS 17+)

- `@Equatable` macro on every navigation view, `AnyView` never
- `@Observable` everywhere, `ObservableObject` / `@Published` never
- Coordinator-owned `NavigationStack` + enum routes, `NavigationLink(destination:)` never
- Coordinator-owned modal state, inline `@State` booleans for sheets never
- Domain layer has zero SwiftUI/UIKit imports

## When to Apply

Reference these guidelines when:
- Designing navigation hierarchies with NavigationStack or NavigationSplitView
- Choosing between push, sheet, and fullScreenCover
- Implementing hero animations, zoom transitions, or gesture-driven dismissals
- Building multi-step flows (onboarding, checkout, registration)
- Using @Observable with @Environment and @Bindable for shared navigation state
- Reviewing code for navigation anti-patterns and Airbnb architecture compliance
- Adding deep linking, state restoration, or tab persistence
- Ensuring VoiceOver and reduce motion support for navigation

## Rule Categories by Priority

| Priority | Category | Impact | Prefix |
|----------|----------|--------|--------|
| 1 | Navigation Architecture | CRITICAL | `arch-` |
| 2 | Navigation Anti-Patterns | CRITICAL | `anti-` |

| 3 | Transition & Animation | HIGH | `anim-` |
| 4 | Modal Presentation | HIGH | `modal-` |
| 5 | Flow Orchestration | HIGH | `flow-` |
| 6 | Navigation Performance | MEDIUM-HIGH | `perf-` |
| 7 | Navigation Accessibility | MEDIUM | `ally-` |
| 8 | State & Restoration | MEDIUM | `state-` |

## Quick Reference

### 1. Navigation Architecture (CRITICAL)

- [`arch-navigation-stack`](references/arch-navigation-stack.md) - Use NavigationStack over deprecated NavigationView
- [`arch-value-based-links`](references/arch-value-based-links.md) - Use value-based NavigationLink over destination closures
- [`arch-destination-registration`](references/arch-destination-registration.md) - Register navigationDestination at stack root
- [`arch-destination-item`](references/arch-destination-item.md) - Use navigationDestination(item:) for optional-based navigation (iOS 17+)
- [`arch-route-enum`](references/arch-route-enum.md) - Define routes as Hashable enums
- [`arch-split-view`](references/arch-split-view.md) - Use NavigationSplitView for multi-column layouts
- [`arch-coordinator`](references/arch-coordinator.md) - Extract navigation logic into Observable coordinator
- [`arch-observable-environment`](references/arch-observable-environment.md) - Use @Environment with @Observable and @Bindable for shared state
- [`arch-deep-linking`](references/arch-deep-linking.md) - Handle deep links by appending to NavigationPath
- [`arch-navigation-path`](references/arch-navigation-path.md) - Use NavigationPath for heterogeneous type-erased navigation
- [`arch-equatable-views`](references/arch-equatable-views.md) - Apply @Equatable macro to every navigation view
- [`arch-observable-only`](references/arch-observable-only.md) - Use @Observable only — never ObservableObject or @Published
- [`arch-no-anyview`](references/arch-no-anyview.md) - Never use AnyView in navigation — use @ViewBuilder or generics
- [`arch-coordinator-modals`](references/arch-coordinator-modals.md) - Present all modals via coordinator — never inline @State

### 2. Navigation Anti-Patterns (CRITICAL)

- [`anti-mixed-link-styles`](references/anti-mixed-link-styles.md) - Avoid mixing NavigationLink(destination:) with NavigationLink(value:)
- [`anti-scattered-destinations`](references/anti-scattered-destinations.md) - Avoid scattering navigationDestination across views
- [`anti-shared-stack`](references/anti-shared-stack.md) - Avoid sharing NavigationStack across tabs
- [`anti-hidden-back-button`](references/anti-hidden-back-button.md) - Avoid hiding back button without preserving swipe gesture
- [`anti-navigation-in-init`](references/anti-navigation-in-init.md) - Avoid heavy work in view initializers
- [`anti-hamburger-menu`](references/anti-hamburger-menu.md) - Avoid hamburger menu navigation
- [`anti-programmatic-tab-switch`](references/anti-programmatic-tab-switch.md) - Avoid programmatic tab selection changes

### 3. Transition & Animation (HIGH)

- [`anim-zoom-transition`](references/anim-zoom-transition.md) - Use zoom navigation transition for hero animations (iOS 18+)
- [`anim-matched-geometry-same-view`](references/anim-matched-geometry-same-view.md) - Use matchedGeometryEffect only within same view hierarchy
- [`anim-spring-config`](references/anim-spring-config.md) - Use modern spring animation syntax (iOS 17+)
- [`anim-gesture-driven`](references/anim-gesture-driven.md) - Use interactive spring animations for gesture-driven transitions
- [`anim-transition-source-styling`](references/anim-transition-source-styling.md) - Style transition sources with shape and background
- [`anim-reduce-motion-transitions`](references/anim-reduce-motion-transitions.md) - Respect reduce motion for all navigation animations
- [`anim-scroll-driven`](references/anim-scroll-driven.md) - Use onScrollGeometryChange for scroll-driven transitions (iOS 18+)

### 4. Modal Presentation (HIGH)

- [`modal-sheet-vs-push`](references/modal-sheet-vs-push.md) - Use push for drill-down, sheet for supplementary content
- [`modal-detents`](references/modal-detents.md) - Use presentation detents for contextual sheet sizing
- [`modal-fullscreen-cover`](references/modal-fullscreen-cover.md) - Use fullScreenCover only for immersive standalone experiences
- [`modal-sheet-placement`](references/modal-sheet-placement.md) - Place .sheet on container view, not on NavigationLink
- [`modal-interactive-dismiss`](references/modal-interactive-dismiss.md) - Guard unsaved changes with interactiveDismissDisabled
- [`modal-nested-navigation`](references/modal-nested-navigation.md) - Use separate NavigationStack inside modals

### 5. Flow Orchestration (HIGH)

- [`flow-tab-independence`](references/flow-tab-independence.md) - Give each tab its own NavigationStack
- [`flow-multi-step`](references/flow-multi-step.md) - Use NavigationStack with route array for multi-step flows
- [`flow-sidebar-navigation`](references/flow-sidebar-navigation.md) - Use NavigationSplitView with selection binding for sidebar
- [`flow-tab-sidebar-adaptive`](references/flow-tab-sidebar-adaptive.md) - Use sidebarAdaptable TabView for iPad tab-to-sidebar (iOS 18+)
- [`flow-pop-to-root`](references/flow-pop-to-root.md) - Implement pop-to-root by clearing NavigationPath
- [`flow-screen-independence`](references/flow-screen-independence.md) - Keep screens independent of parent navigation context

### 6. Navigation Performance (MEDIUM-HIGH)

- [`perf-lazy-destinations`](references/perf-lazy-destinations.md) - Use value-based NavigationLink for lazy destination construction
- [`perf-task-modifier`](references/perf-task-modifier.md) - Use .task for async data loading on navigation
- [`perf-state-object-ownership`](references/perf-state-object-ownership.md) - Own @Observable state with @State, pass as plain property
- [`perf-avoid-body-side-effects`](references/perf-avoid-body-side-effects.md) - Avoid side effects in view body


### 7. Navigation Accessibility (MEDIUM)

- [`ally-rotor-headers`](references/ally-rotor-headers.md) - Mark navigation section headers for VoiceOver rotor
- [`ally-focus-after-navigation`](references/ally-focus-after-navigation.md) - Manage focus after programmatic navigation events
- [`ally-group-navigation-elements`](references/ally-group-navigation-elements.md) - Group related navigation elements to reduce swipe count
- [`ally-hide-decorative-navigation`](references/ally-hide-decorative-navigation.md) - Hide decorative navigation elements from VoiceOver
- [`ally-keyboard-focus`](references/ally-keyboard-focus.md) - Use @FocusState for keyboard navigation in forms

### 8. State & Restoration (MEDIUM)

- [`state-codable-routes`](references/state-codable-routes.md) - Make route enums Codable for navigation persistence
- [`state-scene-storage`](references/state-scene-storage.md) - Use SceneStorage for per-scene navigation persistence
- [`state-tab-persistence`](references/state-tab-persistence.md) - Persist selected tab with SceneStorage
- [`state-deep-link-urls`](references/state-deep-link-urls.md) - Parse deep link URLs into route enums
- [`state-avoid-app-level-path`](references/state-avoid-app-level-path.md) - Avoid defining NavigationPath at App level

## How to Use

Read individual reference files for detailed explanations and code examples:

- [Section definitions](references/_sections.md) - Category structure and impact levels
- [Rule template](assets/templates/_template.md) - Template for adding new rules

## Reference Files

| File | Description |
|------|-------------|
| [references/_sections.md](references/_sections.md) | Category definitions and ordering |
| [assets/templates/_template.md](assets/templates/_template.md) | Template for new rules |
| [metadata.json](metadata.json) | Version and reference information |

Overview

This skill enforces opinionated SwiftUI navigation practices for iOS 17+ apps built with Clean MVVM and a Coordinator pattern. It codifies strict rules—@Equatable views, @Observable-only state, coordinator-owned NavigationStack and modal presentation, and zero legacy navigation APIs—to keep navigation predictable, testable, and performant. Use it to design, implement, or review navigation flows that must match Airbnb-style architecture and accessibility expectations.

How this skill works

The skill inspects navigation code and architecture choices against a prioritized rule set spanning Navigation Architecture, Anti-Patterns, Transitions, Modals, Flow Orchestration, Performance, Accessibility, and State/Restoration. It checks for required annotations (like @Equatable and @Observable), coordinator ownership of NavigationStack and modals, proper route enum usage, and forbidden patterns such as NavigationLink(destination:) or inline @State sheets. It flags violations, suggests corrective actions, and highlights best-fit patterns for transitions and multi-step flows.

When to use it

  • Designing navigation hierarchies with NavigationStack or NavigationSplitView
  • Deciding between push, sheet, and fullScreenCover for a screen
  • Implementing hero animations, zoom transitions, or gesture-driven dismissals
  • Building multi-step flows (onboarding, checkout, registration) with a coordinator
  • Reviewing code for Airbnb Clean MVVM + Coordinator compliance and anti-patterns

Best practices

  • Apply @Equatable to every navigation view and avoid AnyView in navigation stacks
  • Use @Observable everywhere and avoid ObservableObject/@Published
  • Keep NavigationStack and modal state inside an Observable coordinator with enum routes
  • Register navigationDestination at the stack root and use value-based links for lazy destinations
  • Give each tab its own NavigationStack and persist routes via Codable route enums or SceneStorage
  • Respect reduce motion, manage VoiceOver focus after navigation, and avoid side effects in view body

Example use cases

  • Orchestrating a multi-step checkout flow using a coordinator-owned NavigationPath and route enums
  • Choosing sheet vs push: present contextual info as a coordinator-managed sheet with detents
  • Reviewing a codebase to remove NavigationLink(destination:), AnyView, and @Published state
  • Implementing a hero zoom transition for image-driven navigation while honoring reduce motion
  • Designing tabbed apps where each tab maintains its own independent NavigationStack

FAQ

Does this require iOS 17 or later?

Yes. The rules assume SwiftUI APIs and behaviors available in iOS 17+ and forbid legacy navigation patterns.

What if my codebase uses ObservableObject or AnyView extensively?

Migrate incrementally: extract coordinator-owned navigation, replace ObservableObject/@Published with @Observable, and refactor AnyView into generics or @ViewBuilder-based factories.