Flexbox Layout
Flexbox is the only layout system in React Native, and flexDirection defaults to column rather than row.
{ flexDirection: 'row' | 'column', justifyContent: ..., alignItems: ..., flex: number, gap: number }Every View is both a flex container and a flex item. There is no grid, no float, and no document flow to fall back on. Layout is computed by Yoga, React Native's layout engine, which implements most of CSS flexbox and nothing else.
The first difference catches everyone: flexDirection defaults to column, not row. The docs say so explicitly. So children stack vertically until you ask for a row. Once direction is set, justifyContent positions children along the main axis and alignItems along the cross axis. In a row the main axis is horizontal; in a column it is vertical. Those two keep the same effective defaults as CSS flexbox: justifyContent is flex-start and alignItems is stretch, so a child with no width fills the cross axis.
flex and its longhand
flex: 1 is a shorthand. The docs state it equates to flexGrow: 1, flexShrink: 1, flexBasis: 0, and writing those three is identical. flex: 0 means size me from width and height instead. Two siblings with flex: 2 and flex: 1 split the space two to one.
gap and the usual traps
gap, rowGap and columnGap work, but only with numbers; the docs note that only pixel units are supported, so gap: '1rem' is invalid. flexWrap defaults to nowrap, so items shrink instead of moving to a new line. And flex: 1 only fills a parent that itself has a measured size, which is why the chain of flex: 1 usually has to start at the root view.
Example
import { StyleSheet, Text, View } from 'react-native';
export default function FlexboxDemo() {
return (
<View style={styles.screen}>
{/* flexDirection must be asked for: the default is column. */}
<View style={styles.header}>
<Text style={styles.title}>Inbox</Text>
<Text style={styles.count}>12</Text>
</View>
<View style={styles.body}>
<View style={styles.sidebar} />
<View style={styles.content} />
</View>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, backgroundColor: '#ffffff' }, // the chain starts here
header: {
flexDirection: 'row',
justifyContent: 'space-between', // main axis: horizontal in a row
alignItems: 'center', // cross axis: vertical in a row
height: 56,
paddingHorizontal: 16,
},
title: { fontSize: 20, fontWeight: '700' },
count: { fontSize: 15, color: '#636366' },
body: { flex: 1, flexDirection: 'row', gap: 12, padding: 16 },
sidebar: { width: 88, backgroundColor: '#e5e5ea', borderRadius: 8 },
// flex: 1 is the shorthand for flexGrow: 1, flexShrink: 1, flexBasis: 0
content: { flex: 1, backgroundColor: '#f2f2f7', borderRadius: 8 },
});When to use it
- Build a header with a title on the left and an action on the right using justifyContent: 'space-between'.
- Split a screen into a fixed-width sidebar and a content area that takes the rest.
- Space a vertical stack of cards evenly with gap instead of margins on each child.
- Stop a long name from pushing a badge off the edge of a row.
More examples
Longhand flex, and a two-column wrapping layout
Four grey tiles in two rows of two, each just under half the width, with a 12dp gap between rows and the remaining space split between the two footer views. width: '48%' is measured against the parent's content width, and the two footer styles are written out side by side to show the shorthand and its expansion. Use this shape for a tag cloud or a small tile grid of known, short length.
import { StyleSheet, Text, View } from 'react-native';
const items = ['One', 'Two', 'Three', 'Four'];
export default function TileGrid() {
return (
<View style={styles.screen}>
{/* flexWrap is nowrap by default, so it has to be set for rows to break */}
<View style={styles.grid}>
{items.map((label) => (
<View key={label} style={styles.tile}>
<Text style={styles.label}>{label}</Text>
</View>
))}
</View>
{/* These two lines mean exactly the same thing */}
<View style={styles.footerShorthand} />
<View style={styles.footerLonghand} />
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, padding: 16, backgroundColor: '#ffffff' },
grid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
rowGap: 12,
},
tile: { width: '48%', height: 80, borderRadius: 8, backgroundColor: '#e5e5ea' },
label: { textAlign: 'center' },
footerShorthand: { flex: 1 },
footerLonghand: { flexGrow: 1, flexShrink: 1, flexBasis: 0 },
});Letting text shrink next to a fixed-size item
A circular avatar, a name, and a short badge on one line. Without flexShrink: 1 a long name pushes the badge off the edge of the screen, because a row does not wrap by default. With it, the name truncates with an ellipsis and the badge stays visible. This is the fix for almost every row that overflows horizontally.
import { StyleSheet, Text, View } from 'react-native';
type RowProps = { name: string; badge: string };
export default function ListRow({ name, badge }: RowProps) {
return (
<View style={styles.row}>
<View style={styles.avatar} />
{/* flexShrink: 1 lets the Text give up width; numberOfLines then clips it */}
<Text style={styles.name} numberOfLines={1}>
{name}
</Text>
<Text style={styles.badge}>{badge}</Text>
</View>
);
}
const styles = StyleSheet.create({
row: { flexDirection: 'row', alignItems: 'center', gap: 12, padding: 12 },
avatar: { width: 40, height: 40, borderRadius: 20, backgroundColor: '#c7c7cc' },
name: { flexShrink: 1, fontSize: 16 },
badge: { fontSize: 13, color: '#636366' },
});
Discussion