A realistic look at code sharing between mobile and browser
react-native-web renders the same View, Text and ScrollView primitives used on iOS and Android as regular DOM elements in the browser. This article looks at how that translation actually works, where styling and platform APIs create real limits, and how much code genuinely ends up shared once navigation, native hardware access and layout details enter the picture.
Table of Contents
- 1. How react-native-web maps RN primitives onto DOM elements
- 2. Setup in an existing RN project: bundler alias and webpack/Metro config
- 3. Styling differences: StyleSheet, flexbox defaults and media queries
- 4. Platform-specific APIs: Platform.select and .web.tsx file extensions
- 5. Navigation: React Navigation in a web context versus classic web routing
- 6. Limits: native APIs without a web equivalent
- 7. A practical example: a shared button component for both platforms
- 8. A realistic estimate: how much code actually gets shared
- 9. Performance and bundle size considerations for the web target
- 10. Summary
- 11. FAQ
1. How react-native-web maps RN primitives onto DOM elements
react-native-web translates the fundamental React Native components like View, Text, ScrollView and Image at runtime into regular DOM elements, usually div, span and img, while simultaneously translating the StyleSheet API into CSS classes written into the document through its own, highly efficient insertion engine. From the calling component's perspective nothing changes, it keeps importing the same components from react-native, only the build process swaps the target module.
This translation works because React Native primitives were designed from the start to be deliberately platform-independent pure layout and presentation containers, without a fixed binding to UIKit or the Android view system. That very abstraction is what makes react-native-web possible in the first place, while a library that assumes native views directly, without a web fallback, cannot be automatically translated along with it.
2. Setup in an existing RN project: bundler alias and webpack/Metro config
In an existing React Native project, react-native-web is usually wired in through an additional webpack or Vite configuration that redirects the react-native module to react-native-web via an alias, while Metro remains unchanged for native builds. For React Native projects already using Expo, Expo's webpack support handles this redirection largely automatically once the web target is enabled.
It is important to configure bundle splitting cleanly per platform, so that platform-specific code that only exists on iOS or Android does not accidentally end up in the web bundle and fail there at build time. A strict separation via file extensions combined with an explicit resolver order that tells the bundler which extension has priority for which target is the common approach.
3. Styling differences: StyleSheet, flexbox defaults and media queries
React Native defaults to flexDirection: column, while CSS in the browser defaults to row for flexbox direction. react-native-web automatically compensates for this difference for its own primitives, but any CSS written directly outside StyleSheet objects has to account for that difference deliberately, or layouts that look correct on mobile unexpectedly break in the browser.
Media queries do not exist in the original React Native StyleSheet model, which means responsive layouts for the web need additional tools like useWindowDimensions or a dedicated responsive library that reacts to breakpoints at runtime, instead of relying on declarative CSS media queries as the web normally would. This is one of the points where shared code most often needs extra platform logic.
4. Platform-specific APIs: Platform.select and .web.tsx file extensions
For code that fundamentally has to behave differently on one platform, React Native offers two established mechanisms: Platform.select for small, inline branched values within a single file, and file-based platform extensions like Button.web.tsx and Button.native.tsx for entirely different implementations that the bundler automatically picks based on the target platform.
In practice, Platform.select suits small, isolated differences like a slightly different shadow style, while file-based extensions pay off for components that need fundamentally different implementations on web and mobile, for example a map view that relies on a JavaScript library in the browser and a native SDK on mobile.
5. Navigation: React Navigation in a web context versus classic web routing
React Navigation generally supports react-native-web and translates navigation transitions into URL changes via the History API when a matching linking configuration block is provided. For simple stack navigation this works well, but more demanding, web-typical requirements like deeply nested, SEO-relevant server-side routing run into the limits of a navigation system primarily designed for native apps.
Projects with high demands on classic web routing, for example publicly indexed marketing pages, often replace navigation entirely in the web build with a dedicated web framework like Next.js, while shared content components continue to be imported from the common React Native codebase. Navigation itself is one of the areas that realistically tends to stay platform-specific.
6. Limits: native APIs without a web equivalent
Camera access, Bluetooth, native push notifications, biometrics and many other native modules simply have no direct web equivalent, because the browser either offers no access to the corresponding hardware at all or uses a completely different API model, for example the browser's MediaDevices API instead of a native camera SDK. For such cases nothing remains but writing a dedicated web implementation or deliberately disabling the feature in the web build.
Libraries from the React Native ecosystem that do not support a web target themselves, for example specialized native UI components, cannot automatically be used either. Before committing to react-native-web it pays off to take stock of which existing dependencies even ship a web target, rather than discovering that gap in the middle of a project.
7. A practical example: a shared button component for both platforms
A good candidate for genuine code sharing is a simple, presentational component like a button that only needs text, color and a press handler, without any platform-specific native dependency. As long as it uses only React Native primitives, the exact same file works unchanged on iOS, Android and in the web build through react-native-web.
The example below shows exactly such a component, free of any platform-specific branching, yet rendered identically across all three targets.
import { Pressable, Text, StyleSheet } from "react-native";
type Props = {
label: string;
onPress: () => void;
};
export function SharedButton({ label, onPress }: Props) {
return (
<Pressable style={styles.button} onPress={onPress}>
<Text style={styles.label}>{label}</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
button: {
paddingVertical: 12,
paddingHorizontal: 20,
borderRadius: 8,
backgroundColor: "#4338ca",
},
label: {
color: "#ffffff",
fontWeight: "600",
textAlign: "center",
},
});
8. A realistic estimate: how much code actually gets shared
In practice, pure business logic, meaning validation, data processing, API clients and state management, can be shared nearly completely, since these layers are inherently independent of platform-specific UI details. The share of shared code for UI components, on the other hand, drops noticeably once layout nuances, gestures or platform-typical interaction patterns come into play, which mobile and the web expect differently.
Realistic projects often reach a code-sharing rate between 60 and 80 percent for application logic, but noticeably less for the actually visible surface, where hover states, keyboard navigation and web-typical layout patterns need their own adjustments. Anyone setting a 100 percent code-sharing rate as a goal usually underestimates how differently users actually behave on touchscreens versus with a mouse and keyboard.
9. Performance and bundle size considerations for the web target
A web build through react-native-web inevitably brings some runtime overhead, because the StyleSheet-to-CSS translation and the primitive abstraction happen at runtime in the browser instead of being converted into pure, minimal CSS already at build time. For most applications this overhead is negligible, but it can become noticeable on very performance-sensitive marketing or landing pages, where every millisecond of load time counts.
Bundle size can be reduced significantly through consistent code splitting per route and by excluding native, web-unused dependencies from the web bundle. A carefully configured bundler alias that never resolves native-only packages in the web build in the first place prevents unnecessary native code from ending up in the browser bundle and unnecessarily extending initial load time.
| Area | Share of shared code | Typical limitation | Recommendation |
|---|---|---|---|
| Business logic & state | Very high, usually 80-100% | Rarely platform-specific | Keep fully shared |
| API clients & data processing | Very high | Network APIs are mostly platform-independent | Shared modules without platform branches |
| Presentational UI components | Medium, usually 50-70% | Styling details, interaction patterns | Adjust selectively with Platform.select |
| Navigation & routing | Low | Web routing requirements diverge strongly | Often deliberately kept platform-specific |
| Native hardware features | Very low to none | No direct web equivalent | Dedicated web implementation or feature flag |
Mironsoft
React Native app development and Magento integration
A mobile app for the Magento shop that actually runs smoothly?
We build React Native apps cleanly connected to the Magento REST or GraphQL API, from the first line of code to publishing on the App Store and Google Play.
App Concept
Plan the architecture and feature scope of a Magento-connected app together.
Magento API Integration
Cleanly connect product catalog, cart, and checkout to the shop API.
Store Publishing
Guide the App Store and Google Play release process without pitfalls.
10. Summary
React Native Web Code Sharing at a Glance
How it works
react-native-web translates RN primitives into DOM elements at runtime and StyleSheet objects into CSS classes.
Setup
A bundler alias redirects react-native to react-native-web in the web build, Metro stays unchanged for native builds.
Realistic rate
Business logic can be shared almost completely, UI nuances and navigation noticeably less.
Limits
Native hardware features and platform-specific libraries without a web target cannot be used automatically.