react-brain
Browse decisions
React & language foundations 3App architecture 9UI 12Platform & native 9Build, test, observe, secure 6AI in React apps 3stack composerdecision recordscensusstaleness bencharchitecturechangelogmethodologyroadmap

entriesapp-architecture · verified 2026-06-17 · react + react-native

Sharing code across React (web) & React Native

reviewedconfidence: highthis tier holds 75% on the public scorecard (6/11 graded · 0 overturned) →

cited by: styling · component-libs

re-verified 2× — 2026-07-13 · 2026-07-06 · changelog

recommendation

Share a platform-agnostic logic/hooks package first (the highest-leverage, lowest-risk win). For shared UI on NEW projects, evaluate React Strict DOM or Expo Universal Components — not a fresh react-native-web bet.

  • duplicated business logic across web + native → extract a shared workspace package now
  • new universal UI → React Strict DOM or Expo Universal Components
  • existing react-native-web app → keep it (still works); just don't expand the bet
  • Expo + Next.js shared routing → Solito; incremental web reuse inside RN → Expo DOM Components

Options & tradeoffs

the field considered — and why each one isn’t the default here

optiontradeoff
shared logic/hooks packageplatform-agnostic core in a workspace package
platform-extension files (.web.tsx/.native.tsx)one API, per-platform impl
React Strict DOM (Meta)write strict HTML/CSS rendering web + native; Babel flattens to real DOM + static CSS on web; emerging RN-Web successor; involved setup
react-native-webMAINTENANCE MODE (2026) — maps RN primitives to HTML; still works, no major features coming
Tamaguicross-platform UI + optimizing compiler
Expo Universal Components / @expo/uisingle API → SwiftUI (iOS) / Jetpack Compose (Android) / react-dom (web); SDK 56 beta
Solitoshared routing across Expo + Next.js; v5 dropped the RN-Web core dep
Expo DOM Components (`use dom`)render real web components inside RN via a webview wrapper (since Expo SDK 52); good for incremental web reuse

Verified notes

When logic or components are duplicated across a web app and a native app, that duplication is the prime target — extract a shared package. VERIFIED SHIFT (2026): react-native-web has entered MAINTENANCE-ONLY mode — creator Nicolas Gallagher and Zalando pivoted to React Strict DOM, and Software Mansion sees "no major features on the horizon". For NEW universal web+native work, evaluate React Strict DOM or Expo Universal Components rather than a fresh react-native-web bet; existing RN-Web apps keep working. Verified vs official RN sources, Callstack, and Gallagher's notes.

Canonical reading

Editorial annotations on why each piece matters — the articles themselves are the originals; read them there.

use dom — incrementally migrating web UI to native with ReactEvan Bacon (Expo)

Deep-dive on Expo DOM Components and the `use dom` directive — run real web UI inside a native app and migrate to native views component-by-component; covers the Metro bundling, offline support, and honest WebView trade-offs. The primary source for the incremental-reuse path.

AI-Assisted React Native Migration for TV: Lessons From ZattooCallstack (Zattoo case study)

TV/cross-platform RN case study: share until a platform gives a reason to split, telemetry-driven perf, team reorg, and a deflated, realistic account of AI's role. A concrete companion to this entry's share-by-layer thesis.

Sources

Depth (in-domain rules) is owned by the engineering-principles skill — this entry is selection breadth.

The full explanation

The reviewed long-form essay behind this entry — the why, not a how-to. Also on GitHub.

About sharing code across React (web) & React Native

Diataxis: Explanation. This is the conceptual core of why react-brain exists: when a team maintains both a web app and a native app, duplicated logic is the prime target. This page explains how to think about sharing and why the recommendation is shaped the way it is. The option list lives in the index entry RB-E-CROSSPLATFORM.

"Write once, run anywhere" is the wrong frame

The seductive promise is one codebase for web and native. The durable reality is subtler: some layers share almost for free, and one layer — UI — fights you. Teams that try to share everything hit an abstraction tax (leaky Platform.select everywhere, lowest- common-denominator components) and conclude "cross-platform doesn't work." Teams that share by layer get most of the benefit with little of the pain. So the real question is not "share or not" but "which layers, and how far up the stack?"

The layered model (the mental model to keep)

Think of an app as a stack, sharing-friendliness decreasing as you go up:

  1. Logic & hooks — pure functions, business rules, validation, formatting, custom hooks. Platform-agnostic by nature. Share first; this is the highest-leverage, lowest- risk win.
  2. State & data — client state (RB-E-STATE) and the data layer (RB-E-DATA). The leading libraries (Zustand, TanStack Query) are already platform-agnostic, so this layer shares cleanly — put it in the common package.
  3. UI — components and styling. This is where platforms genuinely differ (navigation patterns, gestures, native controls, motion), and where "share everything" goes wrong. Share here only with eyes open, using one of the dedicated approaches below.

The single most useful action for most teams is unglamorous: extract a platform-agnostic logic/hooks package (layers 1–2) into the monorepo and consume it from both apps. That alone removes most duplication and the bugs that come from two drifting copies — and it's reversible and low-risk. Depth on package boundaries and dependency direction is owned by engineering-principles.

The default, and why

Share a logic/hooks package first. For shared UI on new projects, evaluate React Strict DOM or Expo Universal Components — not a fresh react-native-web bet.

The UI clause carries a verified, load-bearing 2026 shift (below). The ordering — logic before UI — is deliberate: it front-loads the cheap, certain wins and defers the hard, opinion-heavy UI decision until you actually need shared components.

The verified shift you must not miss

react-native-web has entered maintenance-only mode. Its creator, Nicolas Gallagher, and early adopter Zalando have pivoted to React Strict DOM (RSD), and Software Mansion's ecosystem read is "no major features on the horizon." This does not mean existing RN-Web apps are in trouble — they keep working. It means a new universal bet should not be placed on RN-Web's continued evolution. This is why the default points elsewhere for greenfield UI sharing. (Sources: Gallagher's "One React for web and native"; Callstack's "From React Native Web to React Strict DOM.")

The deeper reason RSD is the heir: RN-Web works by mapping React Native primitives onto HTML (native-first, web-as-target), whereas RSD inverts it — you write strict HTML and CSS, a Babel step flattens it to real DOM + static CSS on web and to RN styles on native (web-standards-first). That direction aligns with where the platform is going.

The UI-sharing landscape, and when each wins

Platform-extension files (.web.tsx / .native.tsx) — one import, two implementations resolved by the bundler. Simple, explicit, no framework buy-in; the honest default when a handful of components need to diverge. It scales poorly if most components need it (then you wanted a real cross-platform UI layer).

React Strict DOM (Meta) — the strategic bet for new universal UI: write standards-based HTML/CSS once, render web + native. Cost is an involved, multi-step setup and a younger ecosystem — you're betting on the trajectory, not the maturity.

Expo Universal Components / @expo/ui — a single component API that renders as native SwiftUI (iOS) / Jetpack Compose (Android) / react-dom (web). Wins on platform fidelity (real native controls, not bridged approximations) and pairs naturally with an Expo app; the tradeoff is Expo coupling and newness.

Tamagui — cross-platform UI kit with an optimizing compiler; a more batteries-included route when you want a component system rather than primitives.

Solito — not a UI layer but a shared-routing bridge across Expo Router + Next.js, so navigation is one mental model across web and native (see RB-E-NAV).

Expo DOM Components (use dom) — render real web components inside a React Native app via a webview wrapper. The pragmatic escape hatch for incremental reuse of existing web UI in a native shell, not a foundation for a whole app.

Tradeoffs and failure modes to name out loud

  • Sharing UI too aggressively — chasing a single component tree across platforms produces lowest-common-denominator UX. Platforms have conventions (back gestures, tab bars, native pickers); the ecosystem trend is to honor them, not paper over them.
  • The abstraction tax — every Platform.select and per-platform branch is coupling. If a "shared" component is mostly branches, it isn't shared; split it.
  • Rendering RN-on-web vs building web properly — RN-Web-style approaches can yield un-idiomatic, heavier web output. RSD's standards-first direction exists precisely to avoid this.
  • Framework lock-in — Expo Universal Components and Solito tie you to Expo/Next; fine if you're already there, a cost if you're not.

How it interacts with the rest of the stack

  • State & data (RB-E-STATE, RB-E-DATA) — these share cleanly and should live in the common package; they're the easy 80%.
  • Styling (RB-E-STYLING) — RSD is both a code-sharing and a styling strategy (it compiles to static CSS on web); the two entries describe the same tool from two angles.
  • Navigation / meta-frameworks (RB-E-NAV, RB-E-META-FRAMEWORKS) — Solito and Expo Router shape how routing is shared web↔native.
  • Architecture depth — package boundaries, dependency direction, and what may import what are owned by engineering-principles.

In one paragraph

Don't try to "write once, run anywhere"; share by layer. Extract a platform-agnostic logic + state + data package first — the cheap, certain, reversible win. Defer shared UI until you need it, and when you do, bet new work on React Strict DOM or Expo Universal Components, not on react-native-web (now maintenance-only). Honor platform UI conventions rather than flattening them. This layered discipline is precisely the duplication react-brain is built to help teams collapse.


See also: RB-E-STATE & RB-E-DATA (the layers that share cleanly), RB-E-STYLING (React Strict DOM as a styling strategy), RB-E-NAV / RB-E-META-FRAMEWORKS (shared routing). Architecture depth: the engineering-principles skill.

Related in app-architecture: state · data · p2p · nav · meta-frameworks · forms · networking · desktop