---
title: "Handling Navigation in React Native Brownfield Apps"
description: "Learn how to connect React Native and native navigation using a typed bridge with @callstack/brownfield-navigation."
url: https://www.callstack.com/blog/handling-navigation-in-react-native-brownfield-apps
updated: 2026-10-24T08:39:30.375Z
---

# Handling Navigation in React Native Brownfield Apps

Learn how to connect React Native and native navigation using a typed bridge with @callstack/brownfield-navigation.

_By Hur Ali, Software Engineer @ Callstack · Published 2026-03-13 · Category: Brownfield_

Handling navigation and presenting existing native screens from React Native-controlled screens in brownfield applications has traditionally been a tricky part of integration. There are multiple ways to make it work, but the implementation is often non-trivial, especially when you are just getting started with brownfield architecture.

This is exactly why we built: `@callstack/brownfield-navigation`. It helps React Native and native teams wire navigation in a clean, typed, and maintainable way, while improving developer experience on both sides.

Your browser does not support the video tag.

This article is divided into three parts:

- React Native side
- Native App side
- Workflow

## React Native Side

Start by installing the package in your React Native app:

```bash
yarn add @callstack/brownfield-navigation
```

Ensure your app has Babel dependencies available (`@babel/core`, `@react-native/babel-preset`), which are used during codegen. If not, please install them.

Now define your navigation contract in a new file named `brownfield.navigation.ts`:

```typescript
export interface BrownfieldNavigationSpec {
  navigateToSettings(): void;
  navigateToReferrals(userId: string): void;
}
```

Next, run codegen to generate Swift and Kotlin delegates from the schema:

```bash
npx brownfield navigation:codegen
```

You can skip this command in favor of running `npx brownfield package:ios` or `npx brownfield package:android`. These commands internally invoke codegen automatically. If you are not familiar with these commands, [here](https://oss.callstack.com/react-native-brownfield/docs/cli/brownfield#usage) is a quick read.

Finally, invoke the generated methods from React Native to present native screens:

```typescript
import { Button, View } from "react-native";
import BrownfieldNavigation from "@callstack/brownfield-navigation";

export function NativeLinks() {
  return (
    <View>
      <Button
        title="Open native settings"
        onPress={() =>
          BrownfieldNavigation.navigateToSettings()
        }
      />
      <Button
        title="Open native referrals"
        onPress={() =>
          BrownfieldNavigation.navigateToReferrals(
            "user-123",
          )
        }
      />
    </View>
  );
}
```

That is all you need to handle on the React Native side.

> React Native Brownfield is 100% compatible with Expo. If you want to try it on an Expo App, ensure you have followed the [expo-integration](https://oss.callstack.com/react-native-brownfield/docs/getting-started/expo) docs first.

## Native App Side

To complete the wiring, implement the generated delegate in your iOS application. But first, ensure you have the `BrownfieldNavigation.xcframework` linked to the project.

Your browser does not support the video tag.

> Depending on how your setup looks like, this step might differ. Dragging and dropping things into Xcode sounds fun the first time, but it gets tedious pretty fast. This is one of the first things we automate in all the incremental migration projects we do at Callstack.

Then, start by implementing BrownfieldNavigationDelegate:

```swift
import BrownfieldNavigation
import SwiftUI
import UIKit

public final class RNNavigationDelegate: BrownfieldNavigationDelegate {
  public func navigateToSettings() {
    present(SettingsScreen())
  }

  public func navigateToReferrals(_ userId: String) {
    present(ReferralsScreen(userId: userId))
  }

  private func present<Content: View>(_ view: Content) {
    DispatchQueue.main.async {
      let hostingController = UIHostingController(rootView: view)
      UIApplication.shared.topMostViewController()?
        .present(hostingController, animated: true)
    }
  }
}
```

> The func present helper is private and not part of the delegate itself. Depending on your iOS app’s navigation structure, this implementation can vary.

Then register the delegate. An ideal place is before any React Native UI is presented:

```swift
import BrownfieldNavigation

@main
struct BrownfieldAppleApp: App {
  init() {
    BrownfieldNavigationManager.shared.setDelegate(
      navigationDelegate: RNNavigationDelegate()
    )
  }
}
```

That completes the native app setup. You can now run your iOS app and validate the flow.

## Workflow

Codegen turns `brownfield.navigation.ts` into a stable, typed bridge surface that both teams implement against. The React Native team ships that surface inside artifacts (`.xcframework` / `.aar`) and hands those artifacts to native teams. Native apps consume the artifact and wire delegates to existing screens without depending on React Native internals or JavaScript tooling.

This contract-plus-artifact handoff reduces coupling and makes ownership boundaries and delivery steps simpler for both teams.

To summarize, this is how the workflow looks:

1. You create a `brownfield.navigation.ts` spec in your React Native app.
2. You run `npx brownfield navigation:codegen` to generate native bridge/delegate files.
3. React Native calls `BrownfieldNavigation.<method>()`.
4. Native host code implements `BrownfieldNavigationDelegate`.
5. You register the delegate at startup.

Each time you change `brownfield.navigation.ts`:

1. You run `npx brownfield navigation:codegen` to regenerate native bridge/delegate files.
2. Update your React Native call site, if required.
3. Update native host code implementation, if required.

> You can skip `npx brownfield navigation:codegen` in favor of `npx brownfield package:ios` / `npx brownfield package:android`.

> The `codegen` command is intended for development only. When packaging your `xcframework` or `aar`, code generation is handled automatically by the package commands.

## Final Words

`@callstack/brownfield-navigation` is designed to improve developer experience for both native and React Native teams, while reducing long-term maintenance costs for navigation handoff scenarios. It is especially useful in advanced entry points such as deep links and push notifications, where an imperative API can be called from anywhere in React Native.

To learn more about the complete setup and integration details, check out the Brownfield Navigation [docs](https://oss.callstack.com/react-native-brownfield/docs/api-reference/brownfield-navigation.mdx/brownfield-navigation).
