React Native Rendering, Part 5: Yoga — Flexbox Without a Browser
Part 4 left the Shadow Tree full of nodes that describe what to draw — props, state, children — but with no idea where anything goes. Every node is, for now, pure potential. This article is about the stage that turns that potential into concrete geometry.
Producing geometry from styles is called layout, and you already know how the browser does it: CSS, the cascade, the box model, display, floats, and so on. React Native has none of that. Instead, it hands its tree to a small, focused layout engine called Yoga, and asks it one question: given these nodes and these styles, where does everything end up?
What You'll Learn
- Why React Native needed its own layout engine at all — and why it chose Flexbox
- What Yoga is, and how it works on the Shadow Tree
- How a style object in JavaScript becomes layout numbers in C++
- The concrete differences between RN's Flexbox and CSS layouts — and why they exist
- Why Yoga is fast, and how it fits Part 2's "asynchronous layout" story
Prerequisites
- The Shadow Tree, and why it is immutable:
- Why layout in the legacy architecture was asynchronous:
Starting from a Scenario
You come from the web and you write this:
<View style={styles.row}>
<View style={styles.box} />
<View style={styles.box} />
</View>
const styles = StyleSheet.create({
row: { flexDirection: 'row', gap: 8 },
box: { flex: 1, height: 40, backgroundColor: '#7aa' },
});You expect a row of two equal boxes — and you get it. But your instincts keep misfiring:
- Forgetting
flexDirection: 'row'stacks the boxes vertically. On the web, they would be side by side by default. display: block,display: inline,float,clear— none of these exist.height: 50%does not mean "half the screen." Percentages resolve against the parent, and what "the parent" even means depends on the flex algorithm.
None of this is arbitrary. It all follows from one fact: React Native is not running CSS. It is running Yoga.
Core Content
1. Why a phone needs its own layout engine
In Part 1 we asked why React Native could not reuse the browser. The same question returns for layout: why can't it reuse CSS?
Because there is no browser — and therefore no CSS engine — to reuse. The operating system only gives you native views. A UIView placed at "whatever the framework decides" is not a layout system. Something has to compute every view's position and size. React Native had to build that something.
Three requirements shaped the choice:
- Cross-platform. The layout engine must produce the same result on iOS and Android, so a single JS layout renders consistently everywhere. Pure C++, shared across platforms, is the way.
- Declarative and simple. Full CSS includes floats, tables,
.clearfixlore, and a cascade that no one can hold in their head. A mobile UI framework didn't need most of it. - Suited to the problem. Mobile screens are mostly stacked, scrollable, and vertically arranged. Flexbox — a one-dimensional layout model designed for distributing space in rows and columns — fits this almost perfectly, and it was already familiar to web developers.
So React Native wrote Yoga: a cross-platform, C++ implementation of Flexbox. (It is useful enough that it has been used well outside React Native.)
2. What Yoga actually is
Yoga is a layout engine: a function you call with a tree of nodes, each carrying style inputs, that returns each node's computed frame.
Input: Output:
tree of nodes same tree, each node now has
+ styles (flexDirection, justifyContent, x, y, width, height
alignItems, flex, padding, margin, (its final frame)
position, dimensions, gap, ...)It does not paint, does not know about colors, and does not talk to the GPU. It answers exactly one question — where and how big — and nothing else. That focus is part of why it is fast.
3. Inside the algorithm
Yoga walks the tree and, for each node, works out its size and position from its style and its available space. Two ideas matter most:
Intrinsic size comes from the platform. Most nodes have sizes determined purely by Flexbox. But a Text node's natural size depends on the font, the text, and the platform's text engine — iOS and Android measure text differently. Yoga cannot know this. So it calls a measure function that the host provides: "given this text and this width constraint, how big are you?" The renderer supplies that function (in Part 4's terms, the text ShadowNode implements measurement). This is why layout can be mostly pure C++ but not entirely.
Space is distributed in a recursive pass. In broad strokes, for each node Yoga:
- Determines the available space it has to work with (from its parent).
- Computes its children's sizes, letting flexible children grow or shrink to fit.
- Resolves the node's own main-axis size (the direction of
flexDirection), then its cross-axis size. - Positions children along the main axis (
justifyContent) and aligns them on the cross axis (alignItems).
If you have used Flexbox on the web, the vocabulary is identical. What is different is that it runs over a tree with no DOM, no cascade, and no browser — just nodes and styles, in C++.
4. From a JS style object to layout numbers
How does style={{ flex: 1, height: 40 }} end up influencing Yoga? Follow the data:
JS: style={{ flex: 1, height: 40 }}
│ commit (through JSI)
▼
C++: Props struct (type-safe, generated by Codegen from the component's spec)
│ applied to the ShadowNode
▼
Yoga node: flexGrow = 1, height = 40
│ Yoga layout run
▼
Layout metrics: { x, y, width, height } -> stored back on the ShadowNodeSo the styles you write are not "CSS that happens to be in a JS object." They are inputs to a C++ layout engine, and the type-safe bridge in the middle is Codegen (Part 3). That is also why RN can catch many style mistakes for you: the props are strongly typed, not free-form CSS strings.
5. Yoga vs. CSS: the differences that bite
RN's layout is Flexbox, but it is Flexbox-first and CSS-lite. Here are the differences web developers trip over, and the reason behind each:
| Web (CSS) | React Native (Yoga) | Why |
|---|---|---|
flexDirection defaults to row | defaults to column | Mobile UIs stack vertically far more often |
display: block / inline / inline-block | only flex and none | There is no inline text flow to support |
Floats, tables, clearfix | none | Not needed for native UI |
px, em, rem | unitless numbers = dp / points | One consistent, density-independent unit |
height: 50% means "% of some containing block" | resolves against the parent per the flex algorithm | No CSS containing-block model |
| Margin collapsing | none | Simpler, more predictable |
| Cascade + inheritance | none (styles are per-node) | No selectors, no global stylesheet |
position: relative is an offset that keeps space | in RN, position: relative affects zIndex ordering | Different model, same name — a classic trap |
Notice the pattern: every difference exists because the browser's context is gone. No text flow, no document, no CSSOM — so the rules that only made sense inside that context disappear.
6. Why Yoga is fast (and how it fits Part 2)
Three reasons Yoga can compute layout quickly and off the UI thread:
- It is C++, over a clean tree. No DOM traversal, no style resolution against selectors, no cascade recomputation. The input is a tree whose every node already carries typed styles.
- It runs on the background thread, over the immutable Shadow Tree from Part 4. Layout never touches a native view, so it never needs the UI thread.
- Results can be reused. Because unchanged subtrees are structurally shared and their styles do not change, their computed layout can be cached and skipped.
This is also the missing half of the Part 2 story. Back there we said "layout is asynchronous" and blamed the Bridge. Now you can see the other half: layout is asynchronous because it happens on a different thread entirely — the shadow thread, running Yoga — and its result has to travel back to the UI thread to be mounted. Yoga is where the layout work lives; the Bridge is how it got there.
Hands-On Practice
Basic Exercises
-
Make the defaults bite. Build a "row" of two boxes in RN. First omit
flexDirectionand watch them stack. Then addflexDirection: 'row'. This one flipped default is the source of more confusion than any other RN layout rule. -
Predict
flex. Make a row with three children:flex: 1,flex: 2, and a fixed width. Before running it, predict each width. Then check withonLayoutlogs. Adjust until your predictions match. -
Find the measure function. In the React Native source, look for
measureContenton the text ShadowNode. That is the platform-specific text measurement Yoga calls out to.
Advanced Reflection
The browser also runs a layout stage (Part 1's layout). In the browser, layout runs on the main thread, interleaved with your JavaScript; in React Native, Yoga runs on a background thread over an immutable tree. Explain what each design buys and costs — and connect it back to the one-frame layout jump from Part 2.
Summary & Keywords
- A phone has no browser and no CSS engine, so React Native had to build a layout engine. It chose Flexbox, in pure C++, for cross-platform consistency and fit to mobile UIs.
- Yoga takes a tree of nodes plus styles and returns each node's computed frame — and nothing else.
- Intrinsic sizes (text, images) come from a platform measure function; everything else is distributed by a recursive Flexbox pass.
- Styles flow JS style object -> typed C++ Props (via Codegen) -> Yoga node -> layout metrics on the ShadowNode.
- RN's Flexbox differs from CSS in predictable ways (column default, no floats/tables, dp units, no cascade) — each difference exists because the browser's context is gone.
- Yoga is fast because it is C++ over a clean, immutable tree, runs on the background thread, and can reuse cached results. It is the "where" behind Part 2's asynchronous layout.
Keywords: Yoga, Flexbox, layout engine, flexDirection, flexGrow, flexBasis, measure function, density-independent pixels, Shadow Tree, background thread
Further Reading and Next Article Preview
The tree knows what to draw. Yoga has told it where everything goes. Now the renderer has everything it needs to put pixels on the screen — and this is where the new renderer's three-phase pipeline comes together: Render, Commit, Mount. It is the direct counterpart of the browser's style -> layout -> paint -> composite, rebuilt for a world with three threads and an immutable tree.
Next up: React Native Rendering, Part 6: The Fabric Render Pipeline — Render, Commit, Mount.
