React Native Navigation Codebase
Architecture Overview
RNN has three layers that mirror each other:
JS/TS (src/) → TurboModule bridge → iOS native (ios/)
→ Android native (android/)
A navigation command (e.g. push) flows:
Navigation.push() → Commands.ts → processing pipeline → NativeCommandsSender.ts
- TurboModule:
RNNTurboModule (iOS) / NavigationTurboModule.kt (Android)
- iOS:
RNNCommandsHandler → RNNViewControllerFactory → UIKit controllers
- Android:
Navigator → LayoutFactory → View-based controllers (no Fragments)
Read ARCHITECTURE.md for the full overview.
Key Cross-Layer Mappings
Layout Types → Native Controllers
| JS Layout Type |
iOS Controller |
Android Controller |
component |
RNNComponentViewController |
ComponentViewController |
stack |
RNNStackController (UINavigationController) |
StackController |
bottomTabs |
RNNBottomTabsController (UITabBarController) |
BottomTabsController |
sideMenu |
RNNSideMenuViewController (MMDrawerController) |
SideMenuController (DrawerLayout) |
topTabs |
RNNTopTabsViewController |
TopTabsController (ViewPager) |
splitView |
RNNSplitViewController |
N/A (iOS only) |
externalComponent |
RNNExternalViewController |
ExternalComponentViewController |
Options → Presenters
Each controller type has a Presenter that applies options to views:
| iOS Controller |
iOS Presenter |
Android Presenter |
RNNComponentViewController |
RNNComponentPresenter |
ComponentPresenter |
RNNStackController |
RNNStackPresenter + TopBarPresenter |
StackPresenter |
RNNBottomTabsController |
RNNBottomTabsPresenter |
BottomTabsPresenter |
RNNSideMenuViewController |
RNNSideMenuPresenter |
SideMenuPresenter |
Events (same names both platforms)
| Event |
Trigger |
RNN.ComponentDidAppear |
Screen becomes visible |
RNN.ComponentDidDisappear |
Screen hidden |
RNN.NavigationButtonPressed |
TopBar button tap |
RNN.BottomTabSelected |
Tab changed |
RNN.ModalDismissed |
Modal dismissed |
RNN.ScreenPopped |
Screen popped from stack |
RNN.CommandCompleted |
Any command finished |
Where to Find Things
By task: "I need to fix/change X"
| Task |
JS File(s) |
iOS File(s) |
Android File(s) |
| Command execution |
src/commands/Commands.ts |
ios/RNNCommandsHandler.mm |
react/NavigationTurboModule.kt |
| Layout creation |
src/commands/LayoutTreeParser.ts |
ios/RNNViewControllerFactory.mm |
options/LayoutFactory.java |
| Options processing |
src/commands/OptionsProcessor.ts |
ios/RNNNavigationOptions.mm |
options/Options.java |
| Options application |
— |
ios/*Presenter.mm |
viewcontrollers/*Presenter.java |
| TopBar |
src/interfaces/Options.ts (TopBarOptions) |
ios/TopBarPresenter.mm, ios/RNNUIBarButtonItem.mm |
views/stack/topbar/ |
| Bottom tabs |
src/interfaces/Options.ts (BottomTabsOptions) |
ios/RNNBottomTabsPresenter.mm |
viewcontrollers/bottomtabs/ |
| Modals |
src/commands/Commands.ts |
ios/RNNModalManager.mm |
viewcontrollers/modal/ModalStack.java |
| Overlays |
src/commands/Commands.ts |
ios/RNNOverlayManager.mm |
viewcontrollers/overlay/OverlayManager.kt |
| Animations |
src/interfaces/Options.ts (AnimationOptions) |
ios/ScreenAnimationController.mm |
viewcontrollers/stack/StackAnimator.kt |
| React view rendering |
— |
ios/RNNReactView.mm |
react/ReactView.java |
| Events to JS |
src/adapters/NativeEventsReceiver.ts |
ios/RNNEventEmitter.mm |
react/events/EventEmitter.java |
| Component registration |
src/components/ComponentRegistry.ts |
— |
— |
| Deep linking (URL → screen) |
src/linking/ (LinkingHandler, URLParser, RouteMatcher, DeferredLinkQueue, ModalLayoutBuilder) |
ios/RNNAppDelegate.mm (dispatchDeepLinkURL:, cold-start queue, RCTContentDidAppearNotification) |
NavigationActivity.onNewIntent → ReactGateway |
By directory
src/ — JS public API, commands, processing pipeline. See src/ARCHITECTURE.md
ios/ — All Obj-C/C++ native code. See ios/ARCHITECTURE.md
ios/TurboModules/ — New architecture entry points (RNNTurboModule, RNNTurboManager, RNNTurboCommandsHandler)
android/src/main/java/com/reactnativenavigation/ — All Java/Kotlin native code. See android/ARCHITECTURE.md
playground/ — Demo app for development and E2E tests
playground/src/screens/ — Test screens exercising every feature
playground/e2e/ — Detox E2E tests
Options Resolution Order
Options are applied in ascending priority:
- Default options (from
Navigation.setDefaultOptions()) — lowest priority
- Static options (from component class or
Navigation.registerComponent)
- Options passed in the layout call (e.g.
push, setRoot)
mergeOptions() — runtime override, highest priority
JS Processing Pipeline (exact order)
API layout → OptionsCrawler.crawl() → LayoutProcessor.process()
→ LayoutTreeParser.parse() → LayoutTreeCrawler.crawl()
→ OptionsProcessor (colors, assets, custom) → NativeCommandsSender
iOS Patterns
- All controllers conform to
RNNLayoutProtocol
RNNBasePresenter subclasses apply options — applyOptionsOnInit:, applyOptions:, mergeOptions:resolvedOptions:
- Commands run on main thread (
RCTExecuteOnMainQueue)
- React views:
RNNReactView wraps RCTSurfaceHostingView (new arch)
- Overlays use separate
UIWindow instances (RNNOverlayWindow)
RNNReactComponentRegistry caches React component instances
Android Patterns
- View-based, NOT Fragment-based
- All commands dispatched via
UiThread.post()
ViewController<T extends ViewGroup> is the base — createView() is abstract
ParentController extends ChildController extends ViewController
- Bottom tabs use
AHBottomNavigation library
- Three root layouts in
NavigationActivity: rootLayout, modalsLayout, overlaysLayout
- Tab attachment modes:
Together, OnSwitchToTab, AfterInitialTab
Development Workflow
Playground app
yarn start — Metro bundler
yarn xcode — Open iOS project
yarn studio — Open Android project
yarn pod-install — Install iOS pods
Testing
yarn test-js — Jest unit tests
yarn test-unit-ios — iOS native unit tests (XCTest)
yarn test-unit-android — Android native unit tests (JUnit + Robolectric)
yarn test-e2e-ios-ci / yarn test-e2e-android-ci — Detox E2E tests
Building
yarn prepare — Builds src/ → lib/ (ESM + types)
- Codegen config:
rnnavigation in package.json
Common Gotchas
- iOS uses UIKit subclasses (UINavigationController, UITabBarController); Android uses custom View hierarchy
splitView is iOS-only
- Side menu: iOS uses MMDrawerController (3rd party); Android uses DrawerLayout (native)
- Options that exist in JS types may not be implemented on both platforms — check the presenter
passProps are stored in JS Store, not sent to native (cleared before bridge crossing)
- The
lib/ folder is generated — never edit it, edit src/ instead
- Deep links are processed only after the first
setRoot() resolves; pre-bridge URLs on iOS are queued natively in RNNAppDelegate and flushed on RCTContentDidAppearNotification (bridgeless mode — RCTJavaScriptDidLoadNotification does NOT fire)
ModalLayoutBuilder strips React-reserved keys (ref, key) from URL query params before they reach passProps, to avoid React 19 ref-validation crashes
1---2name: rnn-codebase3description: Navigate and work with the react-native-navigation (RNN) codebase. Use when fixing bugs, adding features, tracing command flows, understanding options resolution, or working across JS/iOS/Android layers in this repo.4---56# React Native Navigation Codebase78## Architecture Overview910RNN has three layers that mirror each other:1112```13JS/TS (src/) → TurboModule bridge → iOS native (ios/)14 → Android native (android/)15```1617A navigation command (e.g. `push`) flows:181. `Navigation.push()` → `Commands.ts` → processing pipeline → `NativeCommandsSender.ts`192. TurboModule: `RNNTurboModule` (iOS) / `NavigationTurboModule.kt` (Android)203. iOS: `RNNCommandsHandler` → `RNNViewControllerFactory` → UIKit controllers214. Android: `Navigator` → `LayoutFactory` → View-based controllers (no Fragments)2223Read [ARCHITECTURE.md](../../ARCHITECTURE.md) for the full overview.2425## Key Cross-Layer Mappings2627### Layout Types → Native Controllers2829| JS Layout Type | iOS Controller | Android Controller |30|----------------|---------------|-------------------|31| `component` | `RNNComponentViewController` | `ComponentViewController` |32| `stack` | `RNNStackController` (UINavigationController) | `StackController` |33| `bottomTabs` | `RNNBottomTabsController` (UITabBarController) | `BottomTabsController` |34| `sideMenu` | `RNNSideMenuViewController` (MMDrawerController) | `SideMenuController` (DrawerLayout) |35| `topTabs` | `RNNTopTabsViewController` | `TopTabsController` (ViewPager) |36| `splitView` | `RNNSplitViewController` | N/A (iOS only) |37| `externalComponent` | `RNNExternalViewController` | `ExternalComponentViewController` |3839### Options → Presenters4041Each controller type has a Presenter that applies options to views:4243| iOS Controller | iOS Presenter | Android Presenter |44|----------------|--------------|-------------------|45| `RNNComponentViewController` | `RNNComponentPresenter` | `ComponentPresenter` |46| `RNNStackController` | `RNNStackPresenter` + `TopBarPresenter` | `StackPresenter` |47| `RNNBottomTabsController` | `RNNBottomTabsPresenter` | `BottomTabsPresenter` |48| `RNNSideMenuViewController` | `RNNSideMenuPresenter` | `SideMenuPresenter` |4950### Events (same names both platforms)5152| Event | Trigger |53|-------|---------|54| `RNN.ComponentDidAppear` | Screen becomes visible |55| `RNN.ComponentDidDisappear` | Screen hidden |56| `RNN.NavigationButtonPressed` | TopBar button tap |57| `RNN.BottomTabSelected` | Tab changed |58| `RNN.ModalDismissed` | Modal dismissed |59| `RNN.ScreenPopped` | Screen popped from stack |60| `RNN.CommandCompleted` | Any command finished |6162## Where to Find Things6364### By task: "I need to fix/change X"6566| Task | JS File(s) | iOS File(s) | Android File(s) |67|------|-----------|------------|----------------|68| Command execution | `src/commands/Commands.ts` | `ios/RNNCommandsHandler.mm` | `react/NavigationTurboModule.kt` |69| Layout creation | `src/commands/LayoutTreeParser.ts` | `ios/RNNViewControllerFactory.mm` | `options/LayoutFactory.java` |70| Options processing | `src/commands/OptionsProcessor.ts` | `ios/RNNNavigationOptions.mm` | `options/Options.java` |71| Options application | — | `ios/*Presenter.mm` | `viewcontrollers/*Presenter.java` |72| TopBar | `src/interfaces/Options.ts` (TopBarOptions) | `ios/TopBarPresenter.mm`, `ios/RNNUIBarButtonItem.mm` | `views/stack/topbar/` |73| Bottom tabs | `src/interfaces/Options.ts` (BottomTabsOptions) | `ios/RNNBottomTabsPresenter.mm` | `viewcontrollers/bottomtabs/` |74| Modals | `src/commands/Commands.ts` | `ios/RNNModalManager.mm` | `viewcontrollers/modal/ModalStack.java` |75| Overlays | `src/commands/Commands.ts` | `ios/RNNOverlayManager.mm` | `viewcontrollers/overlay/OverlayManager.kt` |76| Animations | `src/interfaces/Options.ts` (AnimationOptions) | `ios/ScreenAnimationController.mm` | `viewcontrollers/stack/StackAnimator.kt` |77| React view rendering | — | `ios/RNNReactView.mm` | `react/ReactView.java` |78| Events to JS | `src/adapters/NativeEventsReceiver.ts` | `ios/RNNEventEmitter.mm` | `react/events/EventEmitter.java` |79| Component registration | `src/components/ComponentRegistry.ts` | — | — |80| Deep linking (URL → screen) | `src/linking/` (`LinkingHandler`, `URLParser`, `RouteMatcher`, `DeferredLinkQueue`, `ModalLayoutBuilder`) | `ios/RNNAppDelegate.mm` (`dispatchDeepLinkURL:`, cold-start queue, `RCTContentDidAppearNotification`) | `NavigationActivity.onNewIntent` → `ReactGateway` |8182### By directory8384- **`src/`** — JS public API, commands, processing pipeline. See [src/ARCHITECTURE.md](../../src/ARCHITECTURE.md)85- **`ios/`** — All Obj-C/C++ native code. See [ios/ARCHITECTURE.md](../../ios/ARCHITECTURE.md)86- **`ios/TurboModules/`** — New architecture entry points (`RNNTurboModule`, `RNNTurboManager`, `RNNTurboCommandsHandler`)87- **`android/src/main/java/com/reactnativenavigation/`** — All Java/Kotlin native code. See [android/ARCHITECTURE.md](../../android/ARCHITECTURE.md)88- **`playground/`** — Demo app for development and E2E tests89- **`playground/src/screens/`** — Test screens exercising every feature90- **`playground/e2e/`** — Detox E2E tests9192## Options Resolution Order9394Options are applied in ascending priority:951. Default options (from `Navigation.setDefaultOptions()`) — lowest priority962. Static options (from component class or `Navigation.registerComponent`)973. Options passed in the layout call (e.g. `push`, `setRoot`)984. `mergeOptions()` — runtime override, highest priority99100## JS Processing Pipeline (exact order)101102```103API layout → OptionsCrawler.crawl() → LayoutProcessor.process()104 → LayoutTreeParser.parse() → LayoutTreeCrawler.crawl()105 → OptionsProcessor (colors, assets, custom) → NativeCommandsSender106```107108## iOS Patterns109110- All controllers conform to `RNNLayoutProtocol`111- `RNNBasePresenter` subclasses apply options — `applyOptionsOnInit:`, `applyOptions:`, `mergeOptions:resolvedOptions:`112- Commands run on main thread (`RCTExecuteOnMainQueue`)113- React views: `RNNReactView` wraps `RCTSurfaceHostingView` (new arch)114- Overlays use separate `UIWindow` instances (`RNNOverlayWindow`)115- `RNNReactComponentRegistry` caches React component instances116117## Android Patterns118119- View-based, NOT Fragment-based120- All commands dispatched via `UiThread.post()`121- `ViewController<T extends ViewGroup>` is the base — `createView()` is abstract122- `ParentController` extends `ChildController` extends `ViewController`123- Bottom tabs use `AHBottomNavigation` library124- Three root layouts in `NavigationActivity`: rootLayout, modalsLayout, overlaysLayout125- Tab attachment modes: `Together`, `OnSwitchToTab`, `AfterInitialTab`126127## Development Workflow128129### Playground app130- `yarn start` — Metro bundler131- `yarn xcode` — Open iOS project132- `yarn studio` — Open Android project133- `yarn pod-install` — Install iOS pods134135### Testing136- `yarn test-js` — Jest unit tests137- `yarn test-unit-ios` — iOS native unit tests (XCTest)138- `yarn test-unit-android` — Android native unit tests (JUnit + Robolectric)139- `yarn test-e2e-ios-ci` / `yarn test-e2e-android-ci` — Detox E2E tests140141### Building142- `yarn prepare` — Builds `src/` → `lib/` (ESM + types)143- Codegen config: `rnnavigation` in `package.json`144145## Common Gotchas146147- iOS uses UIKit subclasses (UINavigationController, UITabBarController); Android uses custom View hierarchy148- `splitView` is iOS-only149- Side menu: iOS uses MMDrawerController (3rd party); Android uses DrawerLayout (native)150- Options that exist in JS types may not be implemented on both platforms — check the presenter151- `passProps` are stored in JS `Store`, not sent to native (cleared before bridge crossing)152- The `lib/` folder is generated — never edit it, edit `src/` instead153- Deep links are processed only after the first `setRoot()` resolves; pre-bridge URLs on iOS are queued natively in `RNNAppDelegate` and flushed on `RCTContentDidAppearNotification` (bridgeless mode — `RCTJavaScriptDidLoadNotification` does NOT fire)154- `ModalLayoutBuilder` strips React-reserved keys (`ref`, `key`) from URL query params before they reach `passProps`, to avoid React 19 ref-validation crashes