AniUI Academy

Flexbox Layout in React Native

React Native lays out every screen with Flexbox and nothing else, with one classic gotcha — its default flexDirection is column, the opposite of the web's default.

9 min read

If you've styled a page with Flexbox before, most of what follows will feel familiar — flex, justifyContent, alignItems all mean the same thing they do in CSS. But React Native makes one different default choice that catches almost everyone the first time, and it's worth getting straight before you build your first real screen.

Flexbox is the only layout system

Where CSS on the web gives you Flexbox, Grid, floats, and plain block/inline flow to choose between, React Native gives you exactly one: Flexbox. Every View is a flex container by default, and there is no Grid equivalent — anything you'd reach for CSS Grid to build has to be expressed as nested Flexbox containers instead. This is a genuine constraint, not a missing feature waiting to be added; native layout on mobile has leaned on Flexbox as the standard cross-platform model for a long time, and it comfortably covers the layouts real apps need.

The gotcha: flexDirection defaults to column

import { View, Text, StyleSheet } from "react-native";
 
// Default flexDirection: children stack vertically
function StackedByDefault() {
  return (
    <View style={styles.container}>
      <Text>First</Text>
      <Text>Second</Text>
      <Text>Third</Text>
    </View>
  );
}
 
// Explicit row: children now sit side by side
function ExplicitRow() {
  return (
    <View style={[styles.container, { flexDirection: "row" }]}>
      <Text>First</Text>
      <Text>Second</Text>
      <Text>Third</Text>
    </View>
  );
}
 
const styles = StyleSheet.create({
  container: { padding: 12 },
});

If you're used to CSS and reach for a horizontal row of items, you'll get a vertical stack instead unless you remember to add flexDirection: "row". It's worth just memorizing this one, since it comes up on essentially every screen with any horizontal layout — a header bar, a row of buttons, an avatar next to a name.

flex, justifyContent, and alignItems

Once flexDirection is set correctly, the rest of Flexbox behaves the way you already expect from CSS:

  • flex on a child controls how it grows to fill remaining space along the container's main axis. flex: 1 on every child of a row splits the row evenly between them; giving one child flex: 2 gives it twice the share of any other child with flex: 1.
  • justifyContent aligns children along the main axis (horizontal for a row, vertical for a column) — "flex-start", "center", "flex-end", "space-between", "space-around".
  • alignItems aligns children along the cross axis (perpendicular to the main axis) — "flex-start", "center", "flex-end", "stretch".
function Toolbar() {
  return (
    <View
      style={{
        flexDirection: "row",
        justifyContent: "space-between",
        alignItems: "center",
        padding: 12,
      }}
    >
      <Text>Back</Text>
      <Text>Title</Text>
      <Text>Edit</Text>
    </View>
  );
}

Here, flexDirection: "row" lays the three Text elements side by side, justifyContent: "space-between" spreads them across the full width, and alignItems: "center" keeps them vertically centered relative to each other — a standard header-bar layout in three lines.

flexWrap for content that doesn't fit on one line

By default, flex children in a row try to fit on a single line, shrinking if needed. flexWrap: "wrap" lets children flow onto additional lines instead, which is the pattern behind things like a grid of tags or thumbnail images:

function TagList({ tags }) {
  return (
    <View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
      {tags.map((tag) => (
        <View key={tag} style={{ padding: 6, backgroundColor: "#eee", borderRadius: 4 }}>
          <Text>{tag}</Text>
        </View>
      ))}
    </View>
  );
}

A complete example

Putting the column default, an explicit row, and space distribution together into one realistic screen fragment:

function ProductCard({ name, price, imageUrl }) {
  return (
    // Outer View: default column — image on top, details below
    <View style={{ borderRadius: 8, overflow: "hidden", backgroundColor: "#fff" }}>
      <Image source={{ uri: imageUrl }} style={{ width: "100%", height: 160 }} />
      <View style={{ padding: 12 }}>
        {/* Inner row: name and price share the width */}
        <View style={{ flexDirection: "row", justifyContent: "space-between" }}>
          <Text style={{ fontWeight: "600" }}>{name}</Text>
          <Text>{price}</Text>
        </View>
      </View>
    </View>
  );
}

The outer View needs no explicit flexDirection at all, because stacking the image above the details is exactly what the column default already does. Only the inner row, where name and price sit side by side, needs flexDirection: "row" spelled out.

What to remember

  • React Native lays out every screen with Flexbox, and only Flexbox — there's no CSS Grid equivalent.
  • The default flexDirection is column, not row — the opposite of the web's default, and the most common early layout surprise.
  • flex distributes remaining space among children; justifyContent aligns along the main axis; alignItems aligns along the cross axis.
  • flexWrap: "wrap" lets row children flow onto multiple lines instead of shrinking to fit one.
  • When your layout is naturally vertical (like a card with an image above details), you often don't need to set flexDirection at all — the column default already does it.

Check yourself

3 questions · pass 3/3 to unlock StyleSheet and Styling Patterns

up to 50
  1. 1.What is React Native's default flexDirection for a View, and how does it compare to the web's CSS default?

  2. 2.What does flex: 1 do when applied to a child inside a flex container?

  3. 3.Which CSS layout system does React Native support for laying out View trees?

3 left to answer