Same Brain, Different Body
If you already know React, picking up React Native feels familiar — at first. You're still writing JSX, still thinking in components, still reaching for useState and useEffect. The mental model transfers cleanly.
But underneath that familiar surface, the two frameworks are running on completely different machines. The rendering pipeline is different. The styling system is different. Navigation, fonts, images, animations, gestures — all different.
This article is not a beginner's introduction to either framework. It's a map of where they actually diverge, written for developers who've used one and are stepping into the other — or building both simultaneously.
1. The Rendering Pipeline
This is the most fundamental difference, and it shapes everything else.
React (Web)
React renders to the DOM. Your JSX compiles to React.createElement calls, which produce a virtual DOM tree. React diffs that tree against the previous one and applies the minimum number of changes to the real DOM. The browser then handles painting.
// This JSX...
<div className="card">
<h1>Hello</h1>
</div>
// ...compiles to DOM elements the browser understands nativelyReact Native
React Native renders to native UI components — not a browser, not a WebView. On iOS, a <View> becomes a UIView. On Android, it becomes an android.view.View. The JavaScript runs in a separate thread, communicates over a bridge (or the newer JSI layer), and instructs the native layer what to render.
// This JSX...
<View style={styles.card}>
<Text>Hello</Text>
</View>
// ...becomes actual native UI components on iOS and AndroidThis is why React Native apps feel native — because they are native. There's no HTML, no CSS, no browser engine involved. The trade-off is that anything platform-specific requires platform-specific handling.
2. The Styling System
On the web, you have CSS — a mature, cascading, globally-scoped styling language with thousands of properties, pseudo-selectors, media queries, and decades of tooling.
React Native has none of that.
React (Web)
/* CSS — full power of the cascade */
.card {
display: flex;
flex-direction: column;
background-color: #1a1a2e;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
transition: transform 0.2s ease;
}
.card:hover {
transform: translateY(-4px);
}React Native
// StyleSheet — JavaScript objects, no cascade, no pseudo-selectors
const styles = StyleSheet.create({
card: {
flexDirection: 'column',
backgroundColor: '#1a1a2e',
borderRadius: 12,
// No box-shadow — use shadowColor, shadowOffset, elevation (Android)
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 10,
elevation: 8, // Android only
},
});Key differences to internalize:
- No cascade. Styles don't inherit from parent to child (except
TextinsideText). - No pseudo-selectors. No
:hover,:focus,:nth-child. - No units. All values are density-independent pixels by default. No
px,rem,em,vh. - Flexbox by default. Every
Viewis already a flex container.flexDirectiondefaults tocolumn, unlike the web'srow. - Platform-split properties. Shadows work differently on iOS vs Android. Some properties are platform-exclusive.
3. Navigation
On the web, navigation is built into the browser. The URL changes, the history stack updates, the back button works — all for free.
React Native has no browser. There is no URL bar, no history API, no back button (on iOS, you swipe; on Android, there's a hardware/gesture back). You own the entire navigation stack.
React (Web) — React Router
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
export default function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}React Native — React Navigation
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="About" component={AboutScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
// Navigating programmatically
function HomeScreen({ navigation }) {
return (
<Button
title="Go to About"
onPress={() => navigation.navigate('About')}
/>
);
}React Navigation also gives you bottom tabs, drawers, modals, and deep linking — all things the web gets from the browser or the OS. In React Native, you configure every one of them explicitly.
4. Handling User Input & Gestures
On the web, you have onClick, onMouseEnter, onScroll, keyboard events — a well-documented DOM event model.
On mobile, the input model is fundamentally gesture-based. Tap, swipe, pinch, long press, pan — these are the primitives.
React (Web)
<button onClick={handleClick}>Press me</button>
<div onMouseEnter={handleHover}>Hover me</div>React Native
import { TouchableOpacity, Pressable } from 'react-native';
// Basic tap
<TouchableOpacity onPress={handlePress}>
<Text>Press me</Text>
</TouchableOpacity>
// More control — Pressable is the modern approach
<Pressable
onPress={handlePress}
onLongPress={handleLongPress}
style={({ pressed }) => ({
opacity: pressed ? 0.7 : 1,
})}
>
<Text>Press me</Text>
</Pressable>For complex gestures — swipe-to-dismiss, drag-and-drop, pinch-to-zoom — you'll reach for react-native-gesture-handler, which gives you a proper gesture recognizer system that runs on the native thread rather than the JS thread.
5. Performance Model
Both React and React Native use a JavaScript runtime. But how that JS interacts with rendering is architecturally different — and this shapes how you optimize.
React (Web)
JS runs on the main thread alongside the browser's rendering engine. Heavy JS work can block paint. Tools like useMemo, useCallback, React.memo, and code splitting help keep the main thread free.
React Native — The Bridge & JSI
In the original architecture, JS ran on a separate thread and communicated with the native layer via an asynchronous JSON bridge. This was a known bottleneck — especially for animations and gestures that needed frame-perfect timing.
The new architecture introduces JSI (JavaScript Interface), which lets JS call native functions synchronously, and Fabric, a new concurrent renderer. This eliminates much of the bridge overhead.
| Concern | React Web | React Native |
|---|---|---|
| Main bottleneck | JS blocking paint | Bridge / JS thread |
| Animation approach | CSS transitions / Framer Motion | Animated API / Reanimated 2 |
| List performance | Virtualize long lists | FlatList (always virtualized) |
| Image optimization | Next/image, lazy loading | Manual caching strategies |
| Bundle splitting | Webpack / Turbopack | Metro bundler, RAM bundles |
Animations
On the web, CSS animations are off-thread and smooth by default. In React Native, the standard Animated API still runs on the JS thread — meaning heavy JS work can drop frames. The fix is react-native-reanimated, which moves animation logic to the UI thread entirely.
// Reanimated 2 — runs on the UI thread, not JS thread
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
export function AnimatedCard() {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
return (
<Animated.View
style={animatedStyle}
onTouchStart={() => (scale.value = withSpring(0.95))}
onTouchEnd={() => (scale.value = withSpring(1))}
>
<Text>Press me</Text>
</Animated.View>
);
}6. Platform-Specific Code
React Native runs on two platforms with different design languages, different capabilities, and different user expectations. Sometimes you need to diverge.
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
paddingTop: Platform.OS === 'ios' ? 44 : 24,
...Platform.select({
ios: { shadowColor: '#000', shadowOpacity: 0.2 },
android: { elevation: 4 },
}),
},
});For larger divergences, you can use platform-specific file extensions:
# React Native will automatically pick the right one
Button.ios.tsx # used on iOS
Button.android.tsx # used on AndroidThe web has none of this. You write CSS media queries for screen sizes, but you're always targeting a single rendering engine per browser. In React Native, you're targeting two fundamentally different OS ecosystems.
The Architectural Summary
| Dimension | React Web | React Native |
|---|---|---|
| Renders to | DOM (HTML elements) | Native UI components |
| Styling | CSS (cascade, pseudo-selectors) | JS objects (no cascade) |
| Navigation | Browser + React Router | React Navigation (fully manual) |
| Input model | Mouse / keyboard events | Gesture-based (touch, swipe) |
| Animations | CSS / Framer Motion | Animated API / Reanimated |
| Platform split | One rendering engine | iOS + Android diverge |
| Fonts & assets | Web fonts, browser cache | Manually linked, bundled |
| Debugging | Browser DevTools | Flipper, React Native Debugger |
Final Thoughts
React and React Native share a philosophy — build UIs from composable, declarative components. That part transfers. But the moment you go below the component level, you're in different territory.
The developers who struggle with React Native coming from web are usually the ones who expect the web's abstractions to follow them. They don't. CSS doesn't exist here. The browser doesn't exist here. You're closer to the metal than you think.
The developers who thrive are the ones who accept that React Native is its own discipline — one that borrows React's vocabulary but speaks an entirely different language underneath.
Learn both. Respect the difference. Ship accordingly.
