---
title: "React Native in .NET MAUI: A Practical Brownfield Integration Guide"
description: "Learn how to embed React Native inside a .NET MAUI app with a brownfield setup. This guide covers runtime packaging, lifecycle wiring, navigation, and shared state across both runtimes."
url: https://www.callstack.com/blog/react-native-in-net-maui-a-practical-brownfield-integration-guide
updated: 2026-11-09T16:28:20.345Z
---

# React Native in .NET MAUI: A Practical Brownfield Integration Guide

Learn how to embed React Native inside a .NET MAUI app with a brownfield setup. This guide covers runtime packaging, lifecycle wiring, navigation, and shared state across both runtimes.

_By Juozas Petkelis, React Native Developer @ Callstack · Published 2026-08-27 · Category: Brownfield_

MAUI is Microsoft’s cross-platform framework for building native apps with .NET and C#. For companies that already use C# across backend systems and internal engineering teams, that makes it a natural choice. The app team can build mobile products with the same language and ecosystem they already know.

What happens when a team with an existing MAUI app wants to start using React Native? Maybe they have a React web app and would like web developers contribute to mobile apps. Maybe one part of the product would move faster in React Native. Maybe another team already has a React Native screen or module that now needs to live inside the MAUI app. And maybe they want to migrate gradually instead of rewriting everything at once.

Until recently, there was no clear path for that kind of integration. MAUI and React Native are both cross-platform frameworks, and they were not built to plug into each other out of the box.  
This article is for teams in exactly that situation. If you do not have a MAUI app, this probably is not the setup you need. If you do have one and you are considering an incremental move to React Native, embedding a React Native screen inside an existing product, or avoiding a full rewrite, this is where the integration starts to make sense.

We’ll walk through how we made that work: how the React Native runtime is packaged for a MAUI host, how the app lifecycle is wired on iOS and Android, how navigation moves between both sides, and how MAUI and React Native can share data inside one app.

## What is MAUI?

.NET MAUI (Multi-platform App UI) is Microsoft's cross-platform UI framework. You write C# and XAML, and it runs on iOS, Android, Windows, and macOS. It compiles down to native controls. Instead of separate project setups, MAUI gives you one project and one SDK for four platforms. Target framework identifiers like `net10.0-android` and `net10.0-ios` tell the build system which platform to target, and `dotnet build` handles the rest.

The reason MAUI is an interesting technology choice for many enterprises is that it's based on C#, which is embedded into many large companies backend services, allowing teams and developers to share the language they already know and have expertise in.

[`react-native-brownfield`](https://github.com/callstack/react-native-brownfield) has a long history of support for pure native hosts (Swift/Obj-C, Kotlin/Java). What we built is the MAUI variant: a C# package that wraps the same native binding and exposes it as idiomatic .NET.

Before any of that could work, though, we had to convince two very different build systems to agree on what a "dependency" looks like. Here is how it went.

## Integrating MAUI and React Native

The first wall we hit was the build system.

MAUI lives in the NuGet ecosystem, and React Native apps utilize npm, CocoaPods, and Gradle. Before we could think about screens or navigation or shared state, we had to get these two worlds to stop fighting each other. That meant packaging the React Native runtime into a format MAUI's toolchain could actually consume, then wiring the lifecycles together so neither framework stepped on the other's toes.

Your browser does not support the video tag.

### The architecture

#### How a MAUI app is structured

The MAUI app starts in\*\*`MauiProgram.cs`\*\* , where you register  `ReactNativeView` as a first-class MAUI control so the framework knows how to render it. From there, the platform entry points,`AppDelegate` on iOS and `MainActivity` on Android, handle lifecycle events that MAUI doesn't abstract. React Native needs to hook into both. Finally **Shell** is MAUI's URI-based navigation model, which the .NET-side uses to navigate when React Native signals it wants to hand control back.

#### The binding layer

MAUI reaches into native code through **.NET binding projects**: a special project type that wraps a native binary and auto-generates C# bindings. On iOS that binary is an XCFramework, and on Android it's an AAR. The binding project is the seam between .NET and the Objective-C/Java world underneath.

Here's the full stack:

![](https://www.callstack.com/assets/6a96ec2f360e54e3b17f300d_image-(45).png)

On both platforms, `BrownfieldMAUI` exposes the same `DotnetRNBinding` static API, with the consistent method names and semantics across each one, while relying on different native implementation underneath. This means you only have to write the MAUI lifecycle code once for it to work on both platforms.

#### ReactNativeView: simpler than you'd expect

`ReactNativeView` is as thin as a MAUI control can be:

```cpp
public class ReactNativeView : View
{
    public string ModuleName { get; set; }
    public bool CanPopToNative { get; set; }
}
```

`ModuleName` maps to whatever you passed to `AppRegistry.registerComponent` on the JS side, while `CanPopToNative` enables the back-to-native gesture. Together, those two properties make up the entire public API. That's the entire public API. From MAUI's perspective it's just a `View`, so you can drop it in a `Grid`, a `ContentPage`, or in XAML.

### How React Native is packaged for MAUI

React Native doesn't ship as a NuGet package. Before MAUI can talk to it, the runtime has to be compiled and wrapped in the format each platform expects:

- an **AAR** on Android (a zip containing the compiled classes, native `.so` files for Hermes and the RN runtime, and the JS bundle),
- **XCFrameworks** on iOS (fat binaries that carry both device and simulator slices).

Both are then wrapped by .NET binding projects that auto-generate C# bindings from the native outputs, giving `BrownfieldMAUI` a uniform API no matter which platform you're targeting.

### Putting the two runtimes work together

With the runtime packaged and linkable, the next challenge was lifecycle management. React Native wasn't designed to be a guest inside another framework. It expects to own the app entry point and manage its own memory. Getting React Native to behave properly inside MAUI required careful wiring on both platforms.

On iOS, the bootstrapper hooks into `AppDelegate`'s `FinishedLaunching` and registers a navigation delegate while the app is active, clearing it when the app moves to the background. That cleanup matters. If the delegate stays alive in the background, the navigation bridge can keep running when it should not, which can lead to production-only crashes.

Android's strategy is deliberately different, because **the RN host initialises lazily** only when you first request a React Native view. The reason follows from how each platform structures startup. On iOS, `FinishedLaunching` has already fired by the time your app is "running", so you initialize then or never. On Android, the RN host lives inside an Activity that can be spun up on demand, so there's no reason to pay the startup cost for users who never open an RN screen. In practice this means your page waits for the host to signal readiness before handing it to the user.

We had a running React Native screen inside a MAUI app, but it was running in a sealed room, with no way to trigger navigation or share data with the rest of the app.

## How navigation works between React Native and MAUI

Getting RN to run inside MAUI was one thing. Getting it to actually navigate, including opening other pages, responding to the back button, or handing control back to the native side, was another problem entirely.

In a native brownfield app this is relatively straightforward: React Native can call into Swift or Kotlin directly, but in MAUI there's an extra layer. Shell, MAUI's URI-based navigation model, has no idea React Native exists, and JavaScript has no way to reach it. What we needed a typed bridge.

We solved this with a TurboModule, which gave us the New Architecture’s JSI surface with the same name on both platforms. We then defined a TypeScript spec describing which screens are navigable and what each one expects:

```typescript
export interface Spec extends TurboModule {
  navigateToSettings(user: Object): void;
  navigateToReferrals(userId: string): void;
}
```

We ran the `codegen` tool and it generated the Kotlin/ObjC++ glue on the native side and a C# handler interface on the MAUI side. This let us write the spec once, implement one handler, while a navigation call in JavaScript travelled all the way through to `Shell.Current.GoToAsync()`.

MAUI hosts differ from native ones in that the call crosses an extra binding hop and JSON serialization. Payloads arrive as serialized strings and get deserialized into C# models before the Shell call. That extra step comes from the fact that MAUI and React Native do not share a type system, but from React Native's side it's invisible. Back navigation works out of the box, and React Native can optionally take control of the Android back button and iOS swipe-back gesture.

Your browser does not support the video tag.

Navigation told each side who was in charge of the screen. But screens also need data, and keeping both runtimes in sync turned out to be the hardest part.

## Sharing data between MAUI and React Native

Once navigation was working, shared data became the new challenge. Whether it was a signed-in user, a feature flag, or a counter,  changes on the MAUI side needed to be visible in React Native, and vice versa. We could have wired this up with callbacks and module methods, but that approach doesn't scale. Instead, we wanted a shared store as one source of truth that both runtimes could read and write directly, without going through a bridge.

The answer was Brownie. Its source of truth lives in C++: a `folly::dynamic` store behind a mutex, held in a process-wide registry keyed by store name. React Native reaches it through a JSI host object, while MAUI reaches it through a flat C ABI via P/Invoke. Both mutate the same store instance, so there is no syncing, no copying, and no drift between what the two sides think is true.

Your browser does not support the video tag.

The developer-facing API is a TypeScript spec:

```typescript
export interface BrownfieldStore extends BrownieStore {
  counter: number;
}

declare module '@callstack/brownie' {
  interface BrownieStores {
    BrownfieldStore: BrownfieldStore;
  }
}
```

We ran codegen and it emited a shared store key used by JS, native, and MAUI, plus typed structs for Swift and Kotlin.

On the MAUI side, you need to read and write through JSON via P/Invoke, and subscribe to a change callback that fires whenever any client updates the store. The callback tells you which store changed; your handler re-reads and refreshes the UI.

With that, the four walls were down. Two runtimes shared a process, a navigation stack, and a single source of truth.

## Wrapping up

Getting React Native running inside a MAUI app required solving four problems in sequence: build system integration, runtime coexistence, navigation bridging, and shared state. None had established solutions. Most required reading source code for two frameworks that weren't designed to be read side by side.

What we found is that the limitation was not in the platforms themselves, but in the missing integration work. MAUI and React Native can share a process, a navigation stack, and application state, without either framework knowing it's been compromised. Users do not notice the boundary, and for developers the integration remains manageable.

[`react-native-brownfield`](https://github.com/callstack/react-native-brownfield) is free, open-source, and now has MAUI support. If you're working on something in this space and run into something we didn't cover, open an issue or [reach out over email](https://www.callstack.com/contact). The more MAUI hosts are out there, the better the integration gets.
