Mobile Navigation Patterns
Mobile navigation defines how users move between screens in your application. Good navigation is intuitive, predictable, and platform-appropriate.
Navigation Fundamentals
Mobile navigation differs from web navigation:
| Web | Mobile |
|---|---|
| URL-based | Stack-based |
| Back button (browser) | Platform back gesture/button |
| Infinite history | Limited stack depth |
| Multiple windows/tabs | Single screen focus |
Common Navigation Patterns
Stack Navigation
The most fundamental pattern - screens are pushed onto a stack (like a stack of cards) and popped off when navigating back. This creates a hierarchical navigation flow where users can drill deeper into content and easily return to previous screens.
How It Works:
- User starts at Home screen (stack:
[Home]) - Taps "View Details" → Details screen pushed onto stack (stack:
[Home, Details]) - Taps "Edit" → Edit screen pushed onto stack (stack:
[Home, Details, Edit]) - Taps Back → Edit screen popped off (stack:
[Home, Details]) - Taps Back → Details screen popped off (stack:
[Home])
When to Use:
- Primary navigation flow
- Drill-down hierarchies (list → detail → edit)
- Workflows with clear entry and exit points
- Any navigation where users need to retrace their steps
Platform Behaviors:
- iOS: Swipe from left edge to go back, back button in navigation bar
- Android: System back button/gesture, up button in app bar
- React Native: Platform-specific behaviors handled by navigation library
Platform Implementations:
- React Native: React Native Navigation - React Navigation Stack
- iOS: iOS UI - UINavigationController, NavigationStack (SwiftUI)
- Android: Android UI - Navigation Component with NavHost and NavController
Tab Navigation
Switch between main sections of the app via tabs, typically displayed at the bottom of the screen. Each tab represents a distinct section of the app, and tapping a tab instantly switches to that section.
How It Works:
- Tab bar persistently visible at bottom (or top on Android sometimes)
- Each tab can contain its own navigation stack
- Switching tabs preserves each stack's state
- Users can quickly switch between main app sections
When to Use:
- 3-5 top-level sections users switch between frequently
- Sections are equally important and independent
- Users need quick access to all sections without deep navigation
Common Tab Sections:
- Home/Feed
- Search/Explore
- Notifications/Activity
- Messages/Inbox
- Profile/Account
Best Practices:
- Use 3-5 tabs: More than 5 becomes cluttered and hard to use
- Place most important first: Usually Home is leftmost
- Use clear icons: Recognizable, platform-appropriate icons
- Include text labels: Don't rely on icons alone for clarity
- Highlight active tab: Clear visual indication of current section
- Preserve stack state: When user returns to a tab, show their previous position in that section
Platform Conventions:
- iOS: Bottom tab bar, SF Symbols icons, blue highlight
- Android: Bottom navigation bar or top tabs, Material icons, ripple effect
- React Native: Platform-adaptive styling
Platform Implementations:
- React Native: React Native Navigation - Bottom Tab Navigator
- iOS: iOS UI - UITabBarController, TabView (SwiftUI)
- Android: Android UI - BottomNavigationView (Views), NavigationBar (Compose)
Drawer Navigation
Side menu that slides in from the edge of the screen, revealing navigation options. The drawer overlays the main content and can be opened by tapping a menu icon or swiping from the screen edge.
How It Works:
- Hidden by default, overlays main content when opened
- Typically slides in from left edge (sometimes right for secondary menus)
- Opened by menu icon (hamburger), screen edge swipe, or programmatically
- Dismissed by tapping outside, swipe gesture, or selecting an item
When to Use:
- Secondary navigation (settings, account, help)
- Apps with many sections that don't fit in tabs
- Less frequently accessed features
- Contextual actions for current screen
When to Avoid:
- Don't use for primary navigation: Hidden navigation has poor discoverability
- Not for critical features: Users may not find important functions in a drawer
- Avoid on small screens if alternatives exist: Takes up significant space when open
Best Practices:
- Include user profile/account info at top
- Highlight currently active section
- Provide clear close mechanism
- Group related items
- Limit to ~10 items maximum
Platform Implementations:
- React Native: React Native Navigation - Drawer Navigator
- iOS: iOS UI - Custom drawer (not common on iOS)
- Android: Android UI - NavigationView (Views), ModalDrawer (Compose)
Modal Navigation
Overlays that appear on top of the current screen, interrupting the main flow to focus user attention on a specific task. Modals slide up from the bottom (iOS) or fade in (Android) and must be explicitly dismissed.
How It Works:
- Presented over current screen (doesn't push onto navigation stack)
- Slides up from bottom with dismissal gesture (iOS) or appears with backdrop (Android)
- User must complete or cancel the modal task to return to main flow
- Modal has its own navigation context, separate from main navigation
When to Use:
- Forms and creation flows: Create new post, compose message, add item
- Confirmations: Destructive actions, important decisions
- Focused tasks: Share content, select from list, quick actions
- Self-contained workflows: Tasks that don't fit into main navigation hierarchy
When to Avoid:
- Deep workflows: Use stack navigation instead
- Frequent actions: Users will find modals disruptive
- Primary content: Reserve modals for secondary, interruptive tasks
Best Practices:
- Clear dismiss action: X button, Cancel button, or swipe gesture
- No nested modals: Never present a modal from another modal
- Use sparingly: Modals interrupt flow - only for truly separate tasks
- Provide context: Title/header explains what the modal is for
- Handle unsaved changes: Warn before dismissing if data not saved
Platform Conventions:
- iOS: Slide up from bottom, pull down to dismiss, card-style presentation
- Android: Fade in with backdrop, back button dismisses
- React Native: Platform-adaptive presentation styles
Platform Implementations:
- React Native: React Native Navigation - Modal presentation in Stack Navigator
- iOS: iOS UI -
.sheetor.fullScreenCover(SwiftUI) - Android: Android UI - Dialog, BottomSheet, or fullscreen Activity
Platform-Specific Considerations
Each mobile platform has unique navigation conventions and gestures that users expect. Following these platform-specific patterns ensures your app feels native and familiar.
iOS Navigation Patterns
Navigation Bar (Top Bar):
- Back button automatically appears in top-left when navigating deeper
- Title displayed in center or left-aligned (large title)
- Action buttons (Add, Edit, etc.) positioned on the right
- Swipe from left edge to go back (system gesture)
- Long-press back button shows full navigation stack
Tab Bar (Bottom Navigation):
- Bottom-aligned tabs for main app sections
- SF Symbols icons (Apple's icon system)
- Blue highlight for selected tab
- Maximum 5 tabs recommended
- Retains state when switching between tabs
Modal Presentation:
- Slides up from bottom with rounded corners (card style)
- Pull-down gesture to dismiss
- Title and close/cancel button at top
Implementation: See iOS UI for SwiftUI navigation code examples.
Android Navigation Patterns
App Bar (Top Bar):
- Back arrow or up button on left
- Title displayed center or left-aligned
- Action buttons (overflow menu, search, etc.) on right
- System back gesture or button navigates back
Bottom Navigation Bar:
- Bottom-aligned tabs similar to iOS
- Material icons with text labels
- Ripple effect on tap
- 3-5 destinations recommended
- State preserved when switching tabs
Navigation Drawer:
- Slides in from left edge
- Used for secondary navigation (settings, profile, etc.)
- Hamburger menu icon toggles drawer
- More common on Android than iOS
Modal Presentation:
- Fades in with backdrop overlay
- Bottom sheets slide up from bottom
- Back button dismisses modals
Implementation: See Android UI for Jetpack Compose navigation code examples.
Nested Navigation
Combining navigation patterns to create complex, multi-level navigation structures. The most common pattern is tabs containing stacks - each tab has its own independent navigation stack.
Common Nesting Pattern:
Tab Navigator (Root)
├── Home Tab
│ └── Stack Navigator
│ ├── Home Screen
│ ├── Details Screen
│ └── Edit Screen
├── Search Tab
│ └── Stack Navigator
│ ├── Search Screen
│ └── Results Screen
└── Profile Tab
└── Stack Navigator
├── Profile Screen
└── Settings Screen
How It Works:
- Root level uses tab navigation for main app sections
- Each tab contains its own stack navigator
- Switching tabs preserves each stack's state
- User can navigate deep into one tab, switch tabs, and return to find their previous position preserved
Benefits:
- Independent navigation: Each section has its own navigation history
- Preserved state: Switching tabs doesn't lose user's position
- Familiar pattern: Users expect tabs to maintain state
- Clear hierarchy: Top-level sections with deep drill-down capability
Other Nesting Patterns:
- Stack → Modal: Main stack with modals for creation/editing flows
- Drawer → Tabs → Stack: Drawer for secondary nav, tabs for primary, stacks within tabs
- Stack → Tabs: Onboarding stack, then main tabbed interface
Implementation Considerations:
- Keep nesting shallow (2-3 levels maximum)
- Each navigation level should have a clear purpose
- Be mindful of memory - deep nesting can consume resources
Platform Implementations:
- React Native: React Native Navigation - Nested navigators in React Navigation
- iOS: iOS UI - UITabBarController containing UINavigationControllers
- Android: Android UI - BottomNavigationView with multiple NavHostFragments
Deep Linking
Deep linking allows external URLs to open specific screens within your app, rather than just launching the app to its home screen. This creates seamless transitions from web, notifications, and other apps.
How It Works:
- App registers URL schemes and domain associations with the OS
- When user taps a link, OS checks if any installed app handles that URL
- If match found, OS launches app and passes the URL
- App parses URL and navigates to appropriate screen
URL Scheme Types:
-
Custom Scheme (Deep Links):
- Format:
myapp://screen/param - Example:
myapp://profile/123 - Works on all platforms but requires app to be installed
- Format:
-
Universal Links (iOS) / App Links (Android):
- Format:
https://myapp.com/screen/param - Example:
https://myapp.com/items/456 - Falls back to website if app not installed
- Requires server verification (apple-app-site-association, assetlinks.json)
- Format:
Common Use Cases:
- Push notifications: Tap notification → specific screen (order details, message thread)
- Email links: Tap link in email → app content (password reset, verify email)
- Share links: Share content → recipient opens in app
- Marketing campaigns: QR codes, ads → specific app screens
- Cross-app navigation: Another app opens your app to specific screen
URL Structure Example:
Scheme: myapp://
Host: myapp.com
Path: /profile/123
Query: ?tab=posts&sort=recent
Fragment: #section-bio
Full URL: myapp://myapp.com/profile/123?tab=posts&sort=recent#section-bio
Implementation Considerations:
- Handle deep links when app is closed, backgrounded, or already open
- Validate URL parameters before navigation (security)
- Provide fallbacks for invalid or expired links
- Test with app installed and not installed
- Log deep link usage for analytics
Platform-Specific Setup:
- React Native: React Native Navigation - React Navigation linking configuration
- iOS: iOS UI - Universal Links setup with apple-app-site-association file
- Android: Android UI - App Links setup with assetlinks.json file
Navigation State Management
Passing Data Between Screens
There are two primary approaches for passing data between screens:
1. Navigation Parameters (for simple data):
Pass data directly during navigation as parameters/arguments. This is appropriate for:
- Simple data types (strings, numbers, booleans)
- Small objects (IDs, titles, simple configuration)
- Data specific to this navigation transition
Conceptual Pattern:
HomeScreen:
User taps item 42 with title "Payment #123"
Navigate to DetailsScreen with parameters:
- itemId: 42
- title: "Payment #123"
DetailsScreen:
Receives parameters
Uses itemId (42) to fetch full details
Displays title "Payment #123"
Benefits:
- Simple and direct
- No global state pollution
- Easy to test
- Works with deep linking (params map to URL)
Limitations:
- Only serialize simple data (no functions, class instances)
- URL length limits for deep links
- Can become unwieldy with many params
2. Global State (for complex data):
Store data in global state management, navigate with just an ID or trigger, retrieve data from state on destination screen.
Conceptual Pattern:
HomeScreen:
User selects complex payment object
Store in global state: selectedPayment = fullPaymentObject
Navigate to DetailsScreen (no params needed)
DetailsScreen:
Retrieve from global state: selectedPayment
Display full payment details
Benefits:
- Handle complex objects
- Share data across multiple screens
- Works for any data size
- No serialization issues
Limitations:
- Couples screens to global state structure
- Harder to deep link
- Must manage state lifecycle (clear on logout, etc.)
When to Use Each:
- Params: IDs, simple flags, strings, numbers, navigation state
- Global State: Complex objects, data used by multiple screens, user session data
Navigation Guards
Navigation guards intercept navigation attempts to confirm or cancel based on conditions. The most common use case is preventing users from losing unsaved changes.
Common Guard Scenarios:
- Unsaved changes: Warn before leaving edit screen with unsaved data
- Authentication: Redirect to login if accessing protected screen
- Form validation: Prevent advancing in multi-step form if current step invalid
- Confirmation dialogs: Confirm destructive actions before navigating away
Conceptual Pattern (Unsaved Changes):
User editing form on EditScreen
↓ Changes made, hasUnsavedChanges = true
User taps Back button
↓ Navigation guard intercepts
Show confirmation dialog:
"Discard changes?"
- "Don't leave" → Cancel navigation, stay on EditScreen
- "Discard" → Proceed with navigation, data lost
Implementation Approaches:
- Lifecycle hooks: Intercept navigation events (beforeRemove, beforeNavigate)
- Dialog/Alert: Show native confirmation dialog
- Custom modal: Present custom UI for confirmation
- Async validation: Check conditions asynchronously before allowing navigation
Best Practices:
- Only guard when truly necessary (don't overuse)
- Provide clear, specific messages ("Discard changes?" not "Are you sure?")
- Offer save option when appropriate
- Handle both back button and programmatic navigation
- Test all navigation paths (back button, tab switch, deep link)
Resetting Navigation State
Sometimes you need to completely reset the navigation stack, removing all previous screens from history. Common after major state changes.
Use Cases:
- Logout: Clear stack, navigate to login (prevent back to authenticated screens)
- Onboarding completion: Clear onboarding flow, start fresh in main app
- Account switching: Reset to home for new account context
- Error recovery: Clear corrupted navigation state
Conceptual Pattern:
Before Logout:
Stack: [Home, Profile, Settings]
User on Settings screen
After Logout:
Stack: [Login]
All previous screens removed from memory
Back button from Login exits app (or goes to splash)
Benefits:
- Security: Users can't navigate back to previous user's data
- Memory management: Clears all previous screens from memory
- Clean state: Fresh start prevents navigation bugs from accumulating
- Better UX: Users don't encounter confusing back button behavior
Considerations:
- Lose all navigation history
- Can't use back button to return to previous screens
- May need to reload data when re-entering previous sections
- Use sparingly - only for major state transitions
Navigation Performance
Lazy Loading Screens
Lazy loading defers loading screen code until the screen is actually needed, reducing initial app bundle size and improving startup time.
How It Works:
- Screen code not included in initial bundle
- When user navigates to screen, code is fetched and loaded
- Show loading indicator while screen loads
- Subsequent navigations to same screen use cached code
Benefits:
- Faster app startup: Smaller initial bundle
- Lower memory usage: Only active screens in memory
- Better for large apps: Many screens don't all need to load upfront
When to Use:
- Infrequently used screens: Settings, help, rarely accessed features
- Heavy screens: Screens with large dependencies or complex logic
- Conditional features: Screens only some users access
Considerations:
- Brief loading delay on first navigation to screen
- Complexity in code splitting setup
- Not beneficial for frequently used screens (loading delay outweighs bundle savings)
Optimizing Stack Depth
Deep navigation stacks consume memory - each screen in the stack remains in memory even when not visible.
Problem: Deep Stacks
Multi-step Wizard:
Stack: [Home, Step1, Step2, Step3, Step4, Step5]
All 6 screens in memory, but only Step5 visible
Memory usage: High
Solution: Replace Instead of Push
Multi-step Wizard:
Stack: [Home, Step5]
Step 1-4 replaced, not pushed
Memory usage: Low
User can't go back through steps (intentional for wizards)
When to Replace vs. Push:
- Replace: Multi-step flows where back navigation doesn't make sense (wizards, onboarding)
- Push: Normal navigation where users might want to go back
Other Optimization Strategies:
- Reset stack: Clear entire stack after major transitions (logout, onboarding completion)
- Limit tab stacks: Each tab's stack can grow unbounded - consider limits
- Clear deep stacks: After completing deep flows, reset to root screen
Accessibility
Navigation must be accessible to all users, including those using screen readers, switch control, and other assistive technologies.
Key Accessibility Requirements:
-
Accessibility Labels
- All navigation elements must have descriptive labels for screen readers
- Tab icons need text labels, not just icons
- Buttons need clear descriptions ("Go back" not just"Back icon")
-
Touch Targets
- Minimum 44x44 points (iOS) or 48x48 dp (Android)
- Adequate spacing between navigation elements
- Avoid cramming too many tabs or buttons
-
Screen Reader Support
- Announce screen titles when navigating
- Announce tab names when switching tabs
- Provide context for navigation actions ("Navigate to Profile")
-
Focus Management
- Move focus appropriately when screens change
- Ensure back button receives focus when appropriate
- Trap focus within modals
-
Platform Features
- Test with VoiceOver (iOS)
- Test with TalkBack (Android)
- Support Dynamic Type (iOS) / Font scaling (Android)
- Respect reduced motion preferences
Conceptual Pattern:
Tab Navigation:
Each tab has:
- Icon (visual)
- Text label (visual + screen reader)
- Accessibility label (detailed description for screen readers)
- Selected state (visually clear, announced to screen readers)
Screen reader announces:
"Home tab, 1 of 4, selected" (on Home tab)
"Profile tab, 4 of 4" (on other tab)
Testing Checklist:
- Can navigate entire app using only screen reader
- All buttons/tabs have descriptive labels
- Screen changes are announced
- Touch targets meet minimum size
- Supports platform text scaling
- Works with reduced motion enabled
Common Navigation Anti-Patterns
Too Many Tabs
More than 5 tabs becomes cluttered and hard to use.
Solution: Use drawer or categorize into fewer tabs.
Deep Stack Nesting
10+ screens in navigation stack.
Solution: Use modals for side flows, reset stack when appropriate.
Inconsistent Navigation
Different sections use different navigation patterns.
Solution: Establish consistent patterns across the app.
Poor Back Button Behavior
Back button doesn't do what users expect.
Solution: Test navigation thoroughly, follow platform conventions.
Testing Navigation
Navigation must be tested to ensure users can move through the app correctly.
Test Levels:
-
Unit Tests: Test navigation logic in isolation
- Screen components render correctly with navigation params
- Navigation functions called with correct parameters
- Guards trigger correctly based on conditions
-
Integration Tests: Test navigation flows
- Tapping button navigates to correct screen
- Back button returns to previous screen
- Tab switching preserves state
- Deep links open correct screens
-
End-to-End Tests: Test complete user journeys
- User can complete multi-screen workflows
- Navigation state persists across app restart
- Deep links from external sources work correctly
What to Test:
- Happy paths: Common navigation flows work correctly
- Deep linking: URLs navigate to correct screens with correct params
- Guards: Unsaved changes prompt appears and works correctly
- State management: Params passed correctly, global state updated
- Back button: Returns to expected screen in all contexts
- Tab state: Switching tabs preserves navigation stack
- Edge cases: Invalid params, missing screens, interrupted flows
Common Test Scenarios:
Test: User navigates from list to details
1. Render HomeScreen with item list
2. Tap item 42
3. Assert DetailsScreen is visible
4. Assert screen shows data for item 42
Test: Back button from details returns to list
1. Navigate to DetailsScreen
2. Tap back button
3. Assert HomeScreen is visible
4. Assert item list still displayed
Test: Unsaved changes guard works
1. Navigate to EditScreen
2. Make changes to form
3. Tap back button
4. Assert confirmation dialog appears
5. Tap "Discard"
6. Assert navigation completes
Platform-Specific Testing:
- React Native: React Native Testing -
@testing-library/react-nativewith Navigation Container - iOS: iOS Testing - XCUITest for UI testing, unit tests for navigation logic
- Android: Android Testing - Espresso for UI testing, JUnit for navigation logic
Related Guidelines
Platform-Specific Implementation Guides
- React Native Navigation - React Navigation implementation
- iOS UI - SwiftUI NavigationStack and Coordinator pattern
- Android UI - Jetpack Compose Navigation Component
General Mobile Patterns
- Mobile Overview - Cross-platform mobile development patterns
Further Learning
React Native:
iOS:
Android: