> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modaal.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Duet vs React Native: Shared Logic, No JS Runtime

> React Native runs shared logic in a shipped JavaScript engine and draws native views. Duet compiles a shared Kotlin core and verifies parity with recordings.

React Native and Duet agree on one thing most comparisons miss: both draw the interface with native platform views. They part ways on where the logic runs — React Native ships a JavaScript engine in the app and runs your code there, while **Duet**, Modaal's cross-platform parity framework for native iOS and Android apps, compiles a shared Kotlin core into both apps and verifies their agreement with recorded fixtures. Which trade fits depends on your team, your dependencies on native modules, and what you need proven about behavior.

*Last reviewed: September 1, 2026. React Native version and capability claims were checked against React Native's official documentation on this date.*

## How do Duet and React Native differ?

<Frame>
  |                                          | React Native                                                                                                                                                                                                                                                                                                                       | Duet                                                                                                                                  |
  | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
  | Who draws the screen                     | The platform — Fabric composes real UIKit views and Android Views from React components                                                                                                                                                                                                                                            | The platform — SwiftUI on iOS, Jetpack Compose on Android, written per platform                                                       |
  | Where feature logic lives                | Once, in JavaScript/TypeScript, on the Hermes engine the app ships                                                                                                                                                                                                                                                                 | Once, in Kotlin, compiled into both apps as a library                                                                                 |
  | What proves the two platforms agree      | One implementation — until code crosses into a native module                                                                                                                                                                                                                                                                       | Recorded fixtures replayed on both platforms; CI compares byte-for-byte                                                               |
  | The interface source                     | One React component tree, mapped to native views on each platform                                                                                                                                                                                                                                                                  | Two view layers, each authored in its platform's own toolkit                                                                          |
  | Customizing beyond the catalog           | Core components are a small set; anything else is a library or a Native Component written per platform                                                                                                                                                                                                                             | Every control is the platform's own; customization is ordinary SwiftUI and Compose code                                               |
  | A native, perf-heavy island              | Animations can run on the native driver; a camera, video or GPU island is a Fabric Native Component plus a Native Module, written per platform, with its logic outside the JS bundle                                                                                                                                               | Ordinary SwiftUI/UIKit and Compose/Android code, no bridge; the reducer decides, a worker runs the pipeline, and the seam is recorded |
  | Native code you write and compile for it | Yes: Objective-C++ on iOS and Kotlin or Java on Android, compiled by Xcode (through CocoaPods) and Gradle in the app build, with Codegen deriving the native interface from the TypeScript spec; an npm library spares the writing, not the compiling                                                                              | Yes, by design: Swift compiled by Xcode, Kotlin compiled by Gradle                                                                    |
  | SwiftUI and Compose in the island        | Yes, wrapped: a Fabric Native Component hosts a `UIView` or Android `View`, so SwiftUI enters through `UIHostingController` and Compose through `ComposeView`, with Objective-C++ glue on iOS; with Expo, Expo UI builds UI directly with SwiftUI and Jetpack Compose from React, and Expo modules are written in Swift and Kotlin | They are the interface: SwiftUI on iOS, Jetpack Compose on Android, by construction                                                   |
  | Dependency manager on iOS                | CocoaPods (SwiftPM support is experimental as of 0.87)                                                                                                                                                                                                                                                                             | Swift Package Manager and Gradle — the platforms' defaults                                                                            |
</Frame>

## Where does the logic run?

A React Native app ships [Hermes](https://reactnative.dev/docs/hermes), a JavaScript engine tuned for the platform — since React Native 0.84 (February 2026), Hermes V1 is the default on both platforms, with measured wins the team published from Expensify's app: 3.2–9% faster bundle load and 2.5–7.6% faster time-to-interactive. The engine is good; the structural fact stands regardless: your rules execute in a bytecode VM the app carries, one abstraction away from the platform's own execution model.

Duet's shared core is Kotlin compiled against each platform — Kotlin/Native machine code inside the iOS app, ordinary JVM bytecode on Android's ART. There is no bundled language VM or interpreter in either app; the core adds about 3 MB to the Android app, measured on the shipped app Modaal's Duet pages quote.

## Has the New Architecture closed the native gap?

Substantially, on the rendering side. Comparisons that describe an asynchronous bridge describe React Native before 0.82 (October 2025): since that release the old bridge cannot be enabled, and its code has been deleted from default builds since 0.84. [Fabric](https://reactnative.dev/architecture/fabric-renderer) renders synchronously and composes genuine platform views, which is why this page places React Native beside Duet on the who-draws axis, not beside Flutter.

What the New Architecture does not change is the boundary. React Native's [own documentation](https://reactnative.dev/docs/native-platform) states it: platform capabilities not covered by React Native or a library require a Native Module or Native Component, written in Kotlin and Swift/Objective-C per platform. Logic that crosses that line is implemented twice and leaves the shared-behavior guarantee — agreement across the boundary is maintained by review and QA. That is the precise, checkable form of this comparison: not "is React Native native enough," but *how much of your product's logic will live beyond the shared implementation, and what checks it there.* Duet's answer to the same question is that the logic lives in [reducers](/articles/duet-glossary#reducer) — pure, shared, replayed on both platforms — and platform code sits behind [workers](/articles/duet-glossary#worker) whose requests and results are part of the recorded contract: the fixture asserts what was requested and what came back, so the seam itself is inside the verified surface.

The same boundary bounds customization. The [core components](https://reactnative.dev/docs/components-and-apis) are a small set — View, Text, Image, TextInput, Pressable, ScrollView — styled through a cross-platform flexbox subset; a control beyond them comes from a library or is a Native Component written in Swift and Kotlin, and anything outside the React tree — a home-screen widget, a share extension — is platform code regardless. React Native fits UI-neutral business applications whose screens are the same on both platforms; the more the interface must vary by platform, the more of it is written natively, twice, with the framework in between.

## What proves the two platforms agree?

React Native: one implementation, so agreement is by construction inside the JS bundle and by convention beyond it. Duet: both apps replay the same recorded fixtures and CI compares byte-for-byte — 6 of 6 recordings byte-identical across the Swift/Kotlin boundary in the adoption measurement. [Should you share code between iOS and Android?](/articles/share-code-ios-android) develops the by-construction/by-verification distinction; the practical difference is what happens when platforms drift: a bug report in one model, a failing build in the other.

## What if one screen must be native and fast?

React Native's escape is shorter than Flutter's, because the tree is already native views. Animation-heavy screens have a route inside the framework: the [Animated API's native driver](https://reactnative.dev/docs/animations#using-the-native-driver) sends the whole animation to native before it starts, so it runs on the UI thread even when the JavaScript thread is blocked — limited, per the docs, to non-layout properties such as `transform` and `opacity`. A custom-rendered or capture island is a [Fabric Native Component](https://reactnative.dev/docs/fabric-native-components-introduction) — a React component wrapping a host view, for when "core components are not sufficient" — written in Kotlin or Java on Android and Objective-C++ on iOS per the guide, usually paired with a Native Module for the pipeline itself: camera control, video encoding, audio processing.

The drawbacks: the island is written twice, the iOS half is Objective-C++ rather than Swift, its props and events cross the JavaScript boundary, and the pipeline's logic lives in the by-convention zone the boundary section above describes — outside the shared bundle, kept equal by review.

**Do you write and compile native code for it?** Yes, unless an npm library covers the island exactly. A Fabric Native Component is Objective-C++ on iOS and Kotlin or Java on Android, compiled by Xcode and Gradle in the app build, with Codegen deriving the native interface from the TypeScript spec. A library with native code spares you the writing, not the compiling: its native source is built into your app the same way.

**Can the island be SwiftUI or Compose?** Yes, wrapped. A Fabric Native Component hosts a `UIView` on iOS and a `View` on Android, so a SwiftUI view arrives through [`UIHostingController`](https://developer.apple.com/documentation/swiftui/uihostingcontroller) — "a UIKit view controller that manages a SwiftUI view hierarchy" — and Compose through [`ComposeView`](https://developer.android.com/develop/ui/compose/migrate/interoperability-apis/compose-in-views), which "is an Android `View`"; React Native's own component and module guides show the iOS glue in Objective-C++ and mention Swift nowhere. Expo, the framework React Native's documentation recommends, shortens both routes: the [Expo Modules API](https://docs.expo.dev/modules/overview/) lets you "write Swift and Kotlin to add new capabilities to your app with native modules and views" (its reference says rendering SwiftUI views directly is planned, with `UIHostingController` as the current route), and [Expo UI](https://docs.expo.dev/versions/latest/sdk/ui/) is "a set of components that allow you to build UIs directly with Jetpack Compose and SwiftUI from React".

In a Duet app the island is ordinary platform code — AVFoundation and Metal in the SwiftUI app, CameraX and MediaCodec in the Compose app — with nothing between it and the OS. The [reducer](/articles/duet-glossary#reducer) decides when capture starts, which filter applies and what happens with the result; a [worker](/articles/duet-glossary#worker) runs the pipeline. What is recorded and byte-compared is that seam: the requests the reducer makes and the results that come back. The pipeline's internals are outside the verified surface, and the island is written twice, once per platform: Swift compiled by Xcode, Kotlin compiled by Gradle, by design.

## What does React Native do better?

* **The talent pool and the ecosystem.** JavaScript/TypeScript is the largest developer population there is, and npm's breadth has no counterpart in any mobile stack. If your team is web engineers, React Native meets them where they are.
* **One interface implementation.** The React component tree is written once; Fabric maps it to native views on both platforms. Duet's two view layers are written per platform.
* **Framework maturity around the edges.** The project [recommends starting with a framework such as Expo](https://reactnative.dev/docs/environment-setup), and that layer — routing, builds, updates, deployment — is deep and battle-tested.
* **Web from the same skills.** React on the web and React Native on mobile share idioms, components patterns, and often engineers.
* **Governance breadth.** React and React Native moved to the React Foundation under the Linux Foundation in February 2026, with Amazon, Meta, Microsoft, Expo, Callstack and others as founding members — the project's stewardship no longer rests on one company's roadmap.

## When should you pick which?

Pick React Native for UI-neutral business applications — forms, lists, dashboards, content — when your engineering strength is web/TypeScript, native-module and native-component needs are modest or covered by maintained libraries, and one interface implementation matters more than per-platform idiom. Pick Duet when the logic is the risk surface — rules-dense domains where "both apps do the same thing" needs a mechanical check — when you want no second runtime in the binary, or when the interface must be each platform's own toolkit, authored natively.

The framing note this site puts on every comparison: Modaal builds native-first, with both interfaces in the platforms' own toolkits and no shipped runtime — a constraint fixed before any framework evaluation. React Native was ruled out by that constraint, not by a benchmark; this page compares documented properties, not race results.

## Common questions

<AccordionGroup>
  <Accordion title="Is React Native's UI native or not?" icon="mobile-screen">
    The views are native — Fabric composes real UIKit views and Android Views. What is not platform-native is the layer deciding what to render (React, in JavaScript) and the styling system (flexbox everywhere). In practice React Native apps can look and feel very close to native; the remaining gap is idiom and the boundary cases, not the widget identity.
  </Accordion>

  <Accordion title="Doesn't TypeScript give React Native type safety like Kotlin's?" icon="shield">
    At author time, largely yes — 0.87 even makes the Strict TypeScript API the default. The difference is at the boundary and at runtime: types are erased before Hermes executes, and the JS-to-native seam is where mismatches surface. Duet's seam is compiled end to end — a contract change that breaks a caller fails the build on both platforms.
  </Accordion>

  <Accordion title="What about over-the-air updates?" icon="cloud-arrow-down">
    Shipping JavaScript means the bundle can be updated without a store release, within store policies — a genuine operational advantage of the runtime model, and Expo's update service productizes it. A compiled core cannot do this; Duet ships through the stores like any native app.
  </Accordion>

  <Accordion title="We already have a React Native app. Is Duet a migration target?" icon="arrow-right-arrow-left">
    There is no React-Native-to-Duet converter, and this site does not claim one. The honest route is the one the share-code page describes for any rewrite decision: if the app's logic is well-tested and its native-module surface is small, staying put is usually right; a move to Duet is a re-authoring of features against recorded specs, taken one feature at a time.
  </Accordion>

  <Accordion title="Which is better for AI coding agents?" icon="robot">
    React Native offers an agent the largest training corpus of any stack and one language end to end. Duet offers machine-checkable gates: a screen cannot wire to logic without a passing recording, and CI replays the corpus on every push. One optimizes for the agent's fluency, the other for catching the agent's mistakes; the iOS architecture comparison scores the axis in detail.
  </Accordion>
</AccordionGroup>

<Note>
  Duet projects are scaffolded by [Modaal](https://modaal.dev) — [starting a Duet project](/articles/duet#starting-a-duet-project) walks through the two template cards.
</Note>

## Sources and further reading

* [React Native documentation](https://reactnative.dev/docs/getting-started) — the environment-setup framework recommendation, Hermes, releases
* [Native Modules and Native Components](https://reactnative.dev/docs/native-platform) — the official statement of the per-platform boundary
* [Fabric renderer](https://reactnative.dev/architecture/fabric-renderer) — how React components become platform views
* [React Native 0.82](https://reactnative.dev/blog/2025/10/08/react-native-0.82) and [0.84](https://reactnative.dev/blog/2026/02/11/react-native-0.84) release posts — New Architecture-only builds, Hermes V1, legacy code removal
* [The React Foundation announcement](https://www.linuxfoundation.org/press/linux-foundation-announces-the-launch-of-the-react-foundation) — governance since February 2026
* [Fabric Native Components](https://reactnative.dev/docs/fabric-native-components-introduction) and the [Animated native driver](https://reactnative.dev/docs/animations#using-the-native-driver) — the escape-to-native and in-framework routes for a heavy screen
* [Expo Modules API](https://docs.expo.dev/modules/overview/) and [Expo UI](https://docs.expo.dev/versions/latest/sdk/ui/); Apple's [`UIHostingController`](https://developer.apple.com/documentation/swiftui/uihostingcontroller) and Android's [`ComposeView`](https://developer.android.com/develop/ui/compose/migrate/interoperability-apis/compose-in-views) — how SwiftUI and Compose enter a native component
* [The Duet framework on GitHub](https://github.com/modaal-agent/duet) — both flavors, the recording toolchain and the versioned contracts

## Read next

<CardGroup cols={2}>
  <Card title="The best cross-platform frameworks in 2026" icon="map" href="/articles/best-cross-platform-framework">
    The full survey — including the quadrant cell React Native shares with .NET MAUI, and the one Duet shares with KMP practice.
  </Card>

  <Card title="Duet vs Flutter" icon="code-compare" href="/articles/duet-vs-flutter">
    The other one-codebase heavyweight — different answer on who draws the screen.
  </Card>

  <Card title="Should you share code between iOS and Android?" icon="share-nodes" href="/articles/share-code-ios-android">
    The decision one level up: write twice, share everything, or share the logic.
  </Card>

  <Card title="Duet overview" icon="mobile-screen-button" href="/articles/duet">
    What Duet is, the wizard cards that scaffold it, and the six-step loop every feature follows.
  </Card>

  <Card title="How to choose an AI mobile app builder" icon="newspaper" href="https://modaal.dev/blog/how-to-choose-ai-mobile-app-builder">
    What to check before committing to a builder — output, ownership, and the framework it locks you into, on modaal.dev.
  </Card>
</CardGroup>
