React Native Rendering, Part 1: Why React Native Can't Just Reuse the Browser
If you've followed the browser series, you now carry a fairly complete model in your head: a stream of HTML bytes becomes a DOM, CSS becomes a CSSOM, and the engine grinds those two trees through style → layout → paint → composite until pixels appear on the screen.
Then you write your first React Native app, and almost every piece of that model stops applying at once:
- There is no HTML. No
div, nodocument, noquerySelector. - There is no CSS file. You write a JavaScript object and call it
styles. flexDirectiondefaults tocolumn, notrow— the opposite of the web.- If you peek at the running app's view hierarchy, you don't find DOM nodes. You find
UIViews on iOS andandroid.view.ViewGroups on Android.
Did React Native throw the browser away and rebuild everything from scratch? Yes — and that decision is the subject of this entire series.
This article is the ground floor. It answers a single question: why can't React Native reuse the browser's rendering pipeline? Once we answer it, we'll have a map of everything React Native had to build on its own — and that map is the table of contents for the rest of the series.
What You'll Learn
- What React Native really is: not a browser, but a renderer that turns React into native views
- Why the web's whole rendering stack — DOM, CSSOM, layout, paint, compositor — doesn't carry over to native apps
- What React Native had to build by itself as a result, and how each piece maps onto something the browser gives you for free
- Why "cross-platform" is the axis that shapes every architectural decision
Prerequisites
- You've seen how the browser turns a DOM into pixels:
- It helps to know where the DOM comes from in the first place:
- For the foundation underneath everything (processes and threads):
Starting from a Scenario
You've written web apps for years. A friend hands you a React Native project and you open the entry file:
import { View, Text, StyleSheet } from 'react-native';
export default function App() {
return (
<View style={styles.box}>
<Text style={styles.title}>Hello</Text>
</View>
);
}
const styles = StyleSheet.create({
box: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#fff',
},
title: {
fontSize: 20,
color: '#111',
},
});You recognize React immediately — components, JSX, props. But everything around the JSX is subtly wrong, and your web instincts keep firing:
ViewandTextare not HTML tags. They are components that stand in for native views.- The styles are a plain JavaScript object, not a stylesheet. There is no
className, no selector, no cascade. flex: 1andjustifyContentlive right there in the object — you are writing Flexbox by hand.
The natural question is: why does any of this look like this? The answer starts with one sentence.
Core Content
1. The key idea: React is a framework, and a framework needs a renderer
People often say "React renders the UI." That's slightly misleading. React itself never touches real UI.
What React does is turn your components into a description of the UI, and then keep that description in sync with state. That work — components, hooks, reconciliation, the diffing of the element tree — is platform-agnostic. React has no idea whether the other end is a browser, a phone, or a PDF.
The part that actually creates and updates real UI is delegated to a renderer (in React's terms, a host config). Two renderers matter here:
react-domis the web renderer. It knows about HTML tags, produces DOM nodes, and applies styles through the DOM.react-nativeis the native renderer. It knows aboutViewandText, produces native views, and applies styles through native APIs.
So "why can't React Native reuse the browser?" really means: why can't it just reuse react-dom? And the answer is blunt: because react-dom only knows how to talk to a DOM, and there is no DOM on a phone.
2. What the browser gives you for free
Before we take the DOM away, let's be precise about what we'd be losing. When you build a web page, the browser hands you three enormous gifts, and you never write a line of code for any of them:
- A host tree that JavaScript can manipulate. The DOM. It is the living, queryable representation of your page.
- A styling and layout engine. CSS, the cascade, the box model, and flexbox/grid — plus the machinery that turns all of it into coordinates.
- A rendering backend. The paint and compositing stages that eventually talk to the operating system and the GPU.
From the browser series you already know these form one coherent machine:
HTML → DOM
CSS → CSSOM
↓
style → layout → paint → composite (inside the browser engine)The crucial point: on the web, this entire machine comes bundled with "being a web page." You don't assemble it; you inherit it.
3. Why a native app has none of it
A native app is not a document. When your React Native app runs, the pixels are not produced by a browser engine — they are produced by the operating system's own UI toolkit:
- On iOS:
UIView,UILabel, Core Animation. - On Android:
android.view.View,TextView, the Android rendering pipeline.
The OS has never heard of HTML or CSS. There is no engine on the device waiting to parse your markup. So the three gifts above simply do not exist.
You might object: can't I just embed a browser? You can — that's a WebView, and it is exactly what "hybrid" apps do. But look at what you'd get:
- You'd be rendering a web page inside an app, not native UI. Scrolling, gestures, text selection, and accessibility would feel like a website, not like the platform.
- You'd be shipping, or depending on, an entire browser engine — heavy, and impossible to reconcile pixel-precisely with the native elements around it.
- You'd have no control over the layout stages; you'd be a guest in someone else's pipeline.
So if you want UI that is actually native while you write React, someone has to build a renderer whose "host" is native views instead of a DOM. Someone has to build a second renderer. That renderer is React Native.
4. The consequence: React Native must reinvent the pipeline
Once you accept that there is no DOM and no CSS engine, the rest follows logically. Every gift the browser gave you for free, React Native has to build itself:
| Web (for free) | React Native (built itself) |
|---|---|
| DOM — a host tree JS can manipulate | Shadow Tree — a host tree written in C++ |
| CSS + CSSOM — styling and cascade | A styles object + Yoga (a Flexbox engine) |
| Layout engine (box model, flexbox) | Yoga, during the Shadow Tree's layout phase |
| Paint / composite to the screen | Mounting native views + the OS compositor |
| One main thread running the pipeline | Multiple threads (JS, UI, and more) |
Synchronous DOM access (element.offsetHeight) | An async Bridge — or, in the new architecture, JSI |
That table is not a cute analogy. It is the table of contents for this series. Each row is an article:
- Where the Shadow Tree comes from, and why it is React Native's DOM
- Why an entire layout engine (Yoga) had to be written from scratch
- How "mounting" turns a C++ tree into
UIViews and AndroidViews - Why the pipeline is split across threads, and how those threads talk to each other
And here is the part that makes React Native genuinely harder than the browser: the browser's pipeline is one coherent machine running on essentially one thread in one process. React Native takes the same work and splits it across multiple threads and two languages (JavaScript and C++/native). Keeping those pieces in sync — without tearing the UI or blocking the user's finger — is the heart of React Native's architecture. Everything else in this series exists to solve that problem.
5. The cross-platform axis
There is one more pressure the browser never feels: cross-platform.
On the web there is exactly one host platform. In React Native there are (at least) two, and they look nothing alike. One JavaScript tree must become an iOS view tree and an Android view tree:
┌──────────────┐
│ JavaScript │ one tree: your components
└──────┬───────┘
│
┌──────────┴──────────┐
▼ ▼
iOS UIView tree Android View treeThis shapes everything:
- Shared where possible: React, the reconciler, and — increasingly — the parts written in C++ (Yoga, and in the new architecture the entire renderer and Shadow Tree). Writing logic once in C++ means it behaves the same on both platforms.
- Platform-specific where unavoidable: the final mounting step, text measurement, and anything that touches the OS UI toolkit.
This is precisely why, over the years, React Native has moved more and more of itself into C++. Remember this axis; it explains a lot of what comes next.
6. "Native" is not free: the trade-offs
Because React Native uses real native views, you get real native performance and look-and-feel. But inheriting the platform also means giving up the browser's conveniences:
- No full CSS. No selectors, no cascade, no
px/em/%with web semantics. Numbers are density-independent pixels (dp / points). Layout is Flexbox only — nofloat, no CSS grid (yet). - Flexbox defaults flip. On the web,
flexDirectionisrow. In React Native it iscolumn. This trips up everyone coming from the web. - No synchronous DOM. There is no
document.querySelector. You reach native elements through refs, and — in the classic architecture — reading their layout is asynchronous, which is exactly why a tooltip can flash in the wrong place for one frame. That is a story for a later article.
Hands-On Practice
Basic Exercises
-
Port and take inventory. Take a small web component (a centered card with a title and a button) and rewrite it in React Native. Then make a list of every web instinct that broke:
div→View,p/span→Text,className→style, and so on. Keep the list; it is a map of this whole series. -
Flip a default. Write the same row layout in CSS (
display: flex) and in React Native. Notice that you have to writeflexDirection: 'row'in React Native to get what the web gave you for free. -
Look for the DOM and find none. Run the app and open the native view inspector (React Native DevTools, or Xcode's view debugger / Android Studio's Layout Inspector). You will not find DOM nodes — you will find native views. Try to find anything that behaves like
document.
Advanced Reflection
Suppose you embedded a real browser engine inside a native app. Using the three "gifts" from Section 2, argue which one would be fundamentally wrong for a native app — the host tree, the layout engine, or the rendering backend — and why. (Hint: it is not only about performance.)
Summary & Keywords
- React is a framework; a renderer connects it to a host platform.
react-domspeaks the DOM;react-nativespeaks native views. Same React on top, two entirely different bottoms. - A native app has no DOM, no CSSOM, and no browser pipeline. That single fact is why React Native cannot reuse the browser — it must build its own host tree, layout engine, and mounting layer.
- React Native's pipeline maps one-to-one onto the browser's — Shadow Tree ↔ DOM, Yoga ↔ CSS layout, mounting ↔ paint/composite — but it is split across multiple threads and two languages, and keeping those pieces in sync is the core problem the architecture solves.
- Cross-platform is a design axis: share what you can in C++, keep the mounting layer per-platform.
- "Native" is a trade-off: real native UI and performance, at the cost of full CSS, synchronous DOM access, and web layout defaults.
Keywords: renderer, host component, Shadow Tree, Yoga, Flexbox, native view, cross-platform, mounting, thread
Further Reading and Next Article Preview
We now know why React Native had to build its own pipeline. The next question is how it built the first version of it: the classic architecture, where three threads (JavaScript, a layout thread, and the UI thread) cooperate through an asynchronous Bridge that ships serialized messages back and forth.
That Bridge is where React Native's most famous behaviors — and its most famous bugs — come from. Let's go see it.
