Skip to main content

Layout

The engine lays out with flexbox, the same model React Native uses through Yoga. If you know how flex: 1, flexDirection: 'row' and justifyContent: 'space-between' behave on a phone, you know how they behave on a panel. This page is about the edges: what the solver implements, what it leaves out, and what is different about a screen that is 240 pixels wide.

The solver​

layout/layout_engine.c is a Yoga-compatible solve per container, in the same passes Yoga makes: collect the in-flow children with their hypothetical sizes, wrap them into lines, resolve flexGrow and flexShrink against the free space (iteratively, so a child frozen at its minWidth or maxWidth hands its share back to the others), compute each line's cross size, place along the main axis for justifyContent and the cross axis for alignItems/alignSelf, write the results back and recurse, then lay out absolutely positioned children against the parent's padding box.

The properties it takes:

GroupProperties
Sizewidth, height, minWidth, minHeight, maxWidth, maxHeight, aspectRatio
Flexflex, flexGrow, flexShrink, flexBasis, flexDirection, flexWrap
AlignmentjustifyContent, alignItems, alignSelf, alignContent
Spacingmargin*, padding* (each side, Horizontal, Vertical), gap, rowGap, columnGap
Positionposition (relative or absolute), top, left, right, bottom
Visibilitydisplay (flex or none), overflow, zIndex

width, height and flexBasis accept a percentage of the parent's content box, or of its padding box for an absolutely positioned child. So do the four insets: a percentage on left or right is of the containing block's width, on top or bottom of its height. Margins, padding, min/max sizes and borders take pixels only.

Absolute positioning follows Yoga's rules. An axis is pinned by an explicit length, a percentage, or a pair of opposing insets (left and right together). aspectRatio derives the other axis from a pinned one and does nothing when both or neither are pinned. An axis left unresolved sizes to the node's own content, as a flow child would.

Text is measured by the engine's own text layout, in the font and size the node uses, and the measurement is memoised within a pass so a deep tree does not re-measure the same string on the way down. A <Text> with no height is as tall as the lines it wraps into at the width it gets, at most numberOfLines; a text that fits on one line is one line tall. As in Yoga, a child is measured no wider than its parent's content box, so a long <Text> in a row wraps at the row's edge, and one that flexes wraps at the width flexing gives it: flex: 1 text beside a fixed box wraps in the space left, and the row grows to hold it. A row with overflow: 'scroll' offers no bound, so its text stays on one line. Fonts are bitmaps baked at fixed sizes, so a fontSize that was not baked measures at the nearest size that was. See Assets.

What is different​

  • No right-to-left layout. There is no direction property and no start/end edges; left means left.
  • ScrollView scrolls whichever axis overflows. There is no horizontal prop; lay the content out with flexDirection: 'row' and it scrolls sideways. FlatList is a thin wrapper over ScrollView, not a virtualized list: every row is a real node that stays mounted.
  • A fixed node pool. The engine allocates ERUI_MAX_NODES nodes at compile time (512 by default) and never more. A long list has to fit; the Memory guide covers choosing the number.
  • display: 'none' keeps the subtree. A hidden node and everything under it drops out of layout, rendering and hit-testing, but its nodes, props and React state survive. Its computed rectangle collapses to zero, so it takes no space and onLayout reports an empty box. This is the cheap way to switch between pages: build each once, then flip display.
  • onLayout reports the box relative to the parent, as React Native does, before any scroll offset or transform, and also at e.nativeEvent.layout. It fires after the commit that laid the node out. Layout is only current once a commit has run, so code that reads a rectangle part-way through a frame sees the previous commit's answer.

Layout that moves​

LayoutAnimation.configureNext(...) before a state change makes the next commit tween every node whose computed rectangle changed, from where it was to where it now belongs. The tween runs in the engine, with no per-frame JavaScript, and the config is consumed by that one commit. Nodes appearing for the first time snap into place rather than animating from nothing. The presets are the React Native ones: easeInEaseOut, linear and spring.

Designing for small panels​

A phone layout assumes a few hundred logical points of width and a system font that scales. A panel gives you its physical pixels and nothing else: 240 of them on the smallest verified board, 800 on the largest. Two habits carry an app across that range.

  • Read the size. The host injects a screen global with width and height, and both flows let module-level constants derive from it. Pick paddings, font sizes and even whole layouts from screen.width, as the starter and the thermostat demo do, and one file fits every board. In Flow B those values are worked out at boot, and one package holds a layout for each breakpoint, so one build fits every panel.
  • Let flexbox absorb the rest. Percentages, flex: 1 and maxWidth do the same job here as on a phone. A card that is width: '100%' with a maxWidth fills a small panel and centres on a large one without a second layout.

The simulator renders at any size you type, so the layout can be checked at every target before a board is involved.