Module 4: StyleSheet Deep Dive

Flexbox in React Native

Master the layout system that powers every React Native screen

Table of Contents

🎯 Learning Objectives

  • Understand why Flexbox is the only layout system in React Native
  • Master the key differences between web and React Native Flexbox
  • Use flex, flexDirection, justifyContent, and alignItems effectively
  • Control individual item alignment with alignSelf
  • Build common mobile layout patterns using Flexbox
  • Nest flex containers to create complex layouts

Introduction

If you've used CSS Flexbox on the web, you're about to feel right at home — mostly. Flexbox is the only layout system in React Native. There's no CSS Grid, no floats, no display: block or display: inline. Everything is Flexbox, all the time.

This constraint is actually liberating. Instead of choosing between layout systems, you become deeply fluent in one powerful tool. And because mobile interfaces are primarily linear flows — vertical scrolling lists, horizontal tab bars, stacked form fields — Flexbox is perfectly suited for the job.

🎨 The Core Principle

Every View in React Native is a flex container by default. You don't need to write display: 'flex' — it's automatic. Your job is to configure how that flex container arranges its children.

In the previous lesson, you learned StyleSheet fundamentals. Now we'll focus specifically on the flex-related properties that control layout. By the end of this lesson, you'll be able to build any layout you can imagine using just Flexbox.

What We'll Build Understanding Of

Think of the screens you use daily on your phone: a chat app with messages stacked vertically, a bottom tab bar with icons spread horizontally, a card with an image on the left and text on the right. Every one of these layouts is Flexbox at work. We'll break down exactly how to create them.

Flexbox Fundamentals

Before diving into properties, let's establish the mental model. Flexbox works with two types of elements:

  1. Flex Container: The parent element that holds and arranges children
  2. Flex Items: The children inside the container that get positioned
flowchart TB
    subgraph Container["Flex Container (View)"]
        direction TB
        I1["Flex Item 1"]
        I2["Flex Item 2"]
        I3["Flex Item 3"]
    end
    
    Container -.->|"Controls"| Props["• flexDirection
• justifyContent
• alignItems
• flexWrap
• gap"] I1 -.->|"Can override"| ItemProps["• flex
• alignSelf
• flexGrow
• flexShrink"] style Container fill:#e3f2fd style Props fill:#fff3e0 style ItemProps fill:#e8f5e9

The container controls the overall arrangement of items. Individual items can override certain behaviors for themselves. This two-level system gives you both broad control and fine-grained adjustments.

The Default Behavior

Let's see what happens with zero styling — just Views inside a View:

import { View, Text, StyleSheet } from 'react-native';

function DefaultFlexDemo() {
  return (
    <View style={styles.container}>
      <View style={styles.box1}>
        <Text style={styles.text}>Box 1</Text>
      </View>
      <View style={styles.box2}>
        <Text style={styles.text}>Box 2</Text>
      </View>
      <View style={styles.box3}>
        <Text style={styles.text}>Box 3</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
    // No flexDirection specified = column (default)
    // No justifyContent specified = flex-start (default)
    // No alignItems specified = stretch (default)
  },
  box1: { height: 80, backgroundColor: '#ef5350' },
  box2: { height: 80, backgroundColor: '#42a5f5' },
  box3: { height: 80, backgroundColor: '#66bb6a' },
  text: { color: 'white', padding: 10, fontWeight: 'bold' },
});

With these defaults, the boxes stack vertically from top to bottom, each stretching to fill the container's width. This is React Native's default flex behavior.

9:41 Box 1 Box 2 Box 3 Remaining space ← stretch (full width)

Key Differences from Web

If you know CSS Flexbox, you need to internalize these three critical differences:

⚠️ Difference #1: flexDirection Defaults to 'column'

In CSS, flex-direction defaults to row (horizontal). In React Native, flexDirection defaults to 'column' (vertical). This is the most common source of "why doesn't this look right?" confusion.

⚠️ Difference #2: flex Works Differently

In CSS, flex: 1 is shorthand for flex-grow: 1; flex-shrink: 1; flex-basis: 0%. In React Native, flex: 1 means the item will take up available space proportionally. The behavior is similar but not identical, especially with nested containers.

⚠️ Difference #3: No flex-basis, Use width/height

React Native doesn't support flexBasis in the same way. For initial sizing before flex calculations, use width or height depending on your flex direction.

Side-by-Side Comparison

Web CSS Flexbox

.container {
  display: flex;
  /* Default: flex-direction: row */
  /* Default: align-items: stretch */
}

.item {
  flex: 1;
  /* = flex-grow: 1 */
  /* = flex-shrink: 1 */
  /* = flex-basis: 0% */
}

React Native

const styles = StyleSheet.create({
  container: {
    // display: 'flex' is automatic
    // Default: flexDirection: 'column'
    // Default: alignItems: 'stretch'
  },
  item: {
    flex: 1,
    // Takes proportional space
    // along main axis
  },
});

The table below summarizes property names and defaults:

Property CSS Default RN Default
flexDirection row column
justifyContent flex-start flex-start
alignItems stretch stretch
flexWrap nowrap nowrap
alignContent stretch flex-start

Main Axis vs Cross Axis

Understanding Flexbox requires understanding its two axes. Every flex container has:

  • Main Axis: The primary direction items flow (set by flexDirection)
  • Cross Axis: The perpendicular direction

The axis concept is crucial because different properties control different axes:

  • justifyContent → controls alignment on the main axis
  • alignItems → controls alignment on the cross axis
flexDirection: 'column' (React Native default) MAIN AXIS CROSS AXIS flexDirection: 'row' (CSS default) MAIN AXIS CROSS AXIS

💡 Memory Trick

JustifyContent = Journey along main axis (where items travel)
AlignItems = Across the cross axis (perpendicular)

When flexDirection changes, the axes swap. This is why the same justifyContent: 'center' can center items horizontally in a row or vertically in a column — it always works on the main axis, whatever direction that is.

The flex Property

The flex property is how items claim space within their container. It's a number that represents the item's share of available space.

How flex Works

function FlexDemo() {
  return (
    <View style={styles.container}>
      <View style={[styles.box, { flex: 1, backgroundColor: '#ef5350' }]}>
        <Text style={styles.text}>flex: 1</Text>
      </View>
      <View style={[styles.box, { flex: 2, backgroundColor: '#42a5f5' }]}>
        <Text style={styles.text}>flex: 2</Text>
      </View>
      <View style={[styles.box, { flex: 1, backgroundColor: '#66bb6a' }]}>
        <Text style={styles.text}>flex: 1</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1, // Container takes full available space
  },
  box: {
    justifyContent: 'center',
    alignItems: 'center',
  },
  text: {
    color: 'white',
    fontWeight: 'bold',
    fontSize: 16,
  },
});

The total flex is 1 + 2 + 1 = 4 parts. Red gets 1/4, blue gets 2/4 (half), green gets 1/4.

flex: 1 (1/4 of space) flex: 2 (2/4 of space) flex: 1 (1/4 of space) 25% 50% 25%

flex: 0 vs No flex

Understanding what happens without flex is equally important:

const styles = StyleSheet.create({
  // Takes all available space
  fillSpace: {
    flex: 1,
  },
  
  // Takes only the space its content needs
  fitContent: {
    // No flex property = size to content
  },
  
  // Explicitly zero - same as fitContent but clearer
  noFlex: {
    flex: 0,
  },
  
  // Fixed size, ignores flex
  fixedSize: {
    width: 100,
    height: 100,
  },
});

✅ Practical Patterns

  • flex: 1 on a container = "fill the entire screen"
  • flex: 1 on list items = "divide space equally"
  • No flex on buttons = "size to fit the text"
  • Fixed height on headers = "always 60px tall"

Common Layout: Header, Content, Footer

function ScreenLayout() {
  return (
    <View style={styles.screen}>
      {/* Fixed height header */}
      <View style={styles.header}>
        <Text style={styles.headerText}>Header</Text>
      </View>
      
      {/* Flexible content area - takes remaining space */}
      <View style={styles.content}>
        <Text>Content fills remaining space</Text>
      </View>
      
      {/* Fixed height footer */}
      <View style={styles.footer}>
        <Text style={styles.footerText}>Footer</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  screen: {
    flex: 1,  // Fill the whole screen
  },
  header: {
    height: 60,  // Fixed height, no flex
    backgroundColor: '#2196F3',
    justifyContent: 'center',
    paddingHorizontal: 16,
  },
  headerText: {
    color: 'white',
    fontSize: 18,
    fontWeight: 'bold',
  },
  content: {
    flex: 1,  // Takes ALL remaining space
    padding: 16,
  },
  footer: {
    height: 50,  // Fixed height, no flex
    backgroundColor: '#f5f5f5',
    borderTopWidth: 1,
    borderTopColor: '#e0e0e0',
    justifyContent: 'center',
    alignItems: 'center',
  },
  footerText: {
    color: '#666',
  },
});

flexDirection

The flexDirection property sets the main axis direction. It has four possible values:

  • 'column' — Top to bottom (default in React Native)
  • 'row' — Left to right
  • 'column-reverse' — Bottom to top
  • 'row-reverse' — Right to left
const styles = StyleSheet.create({
  column: {
    flexDirection: 'column',  // ↓ Default - items stack vertically
  },
  row: {
    flexDirection: 'row',     // → Items line up horizontally
  },
  columnReverse: {
    flexDirection: 'column-reverse',  // ↑ Bottom to top
  },
  rowReverse: {
    flexDirection: 'row-reverse',     // ← Right to left
  },
});
column (default) 1 2 3 row 1 2 3 column-reverse 3 2 1 row-reverse 3 2 1 Common Uses: • column: Screen layouts, forms, lists • row: Tab bars, button groups, cards with image + text • column-reverse: Chat messages (newest at bottom) • row-reverse: RTL languages, right-aligned action buttons

Example: Horizontal Button Group

function ButtonGroup() {
  return (
    <View style={styles.buttonGroup}>
      <Pressable style={styles.button}>
        <Text style={styles.buttonText}>Cancel</Text>
      </Pressable>
      <Pressable style={[styles.button, styles.primaryButton]}>
        <Text style={[styles.buttonText, styles.primaryText]}>Save</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  buttonGroup: {
    flexDirection: 'row',  // Buttons side by side
    gap: 12,               // Space between buttons
    padding: 16,
  },
  button: {
    flex: 1,              // Each button takes equal space
    paddingVertical: 12,
    borderRadius: 8,
    backgroundColor: '#e0e0e0',
    alignItems: 'center',
  },
  primaryButton: {
    backgroundColor: '#2196F3',
  },
  buttonText: {
    fontWeight: '600',
    color: '#333',
  },
  primaryText: {
    color: '#fff',
  },
});

justifyContent

justifyContent controls how items are distributed along the main axis. Think of it as answering: "How should I space out my items in the direction they're flowing?"

The available values are:

  • 'flex-start' — Pack items at the start (default)
  • 'flex-end' — Pack items at the end
  • 'center' — Center items
  • 'space-between' — Equal space between items, none at edges
  • 'space-around' — Equal space around items (half space at edges)
  • 'space-evenly' — Equal space between and at edges
flex-start (default) flex-end center space-between space-around ½ ½ space-evenly When to Use Each: • flex-start: Default lists, forms, stacked content • flex-end: Right-align buttons, bottom-align content • center: Centering a modal, loading spinner, empty states • space-between: Tab bars, navigation with logo left & icons right • space-around: Icon grids, evenly distributed options • space-evenly: Perfect equal spacing in all gaps

Example: Tab Bar with space-between

function TabBar() {
  return (
    <View style={styles.tabBar}>
      <Pressable style={styles.tab}>
        <Text>🏠</Text>
        <Text style={styles.tabLabel}>Home</Text>
      </Pressable>
      <Pressable style={styles.tab}>
        <Text>🔍</Text>
        <Text style={styles.tabLabel}>Search</Text>
      </Pressable>
      <Pressable style={styles.tab}>
        <Text>❤️</Text>
        <Text style={styles.tabLabel}>Favorites</Text>
      </Pressable>
      <Pressable style={styles.tab}>
        <Text>👤</Text>
        <Text style={styles.tabLabel}>Profile</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  tabBar: {
    flexDirection: 'row',
    justifyContent: 'space-around', // Even spacing for tabs
    paddingVertical: 10,
    backgroundColor: '#fff',
    borderTopWidth: 1,
    borderTopColor: '#e0e0e0',
  },
  tab: {
    alignItems: 'center',
  },
  tabLabel: {
    fontSize: 12,
    marginTop: 4,
    color: '#666',
  },
});

alignItems

alignItems controls how items are aligned along the cross axis. Think of it as answering: "How should items be positioned perpendicular to the main flow?"

The available values are:

  • 'stretch' — Items stretch to fill the cross axis (default)
  • 'flex-start' — Items align to the start of cross axis
  • 'flex-end' — Items align to the end of cross axis
  • 'center' — Items center on the cross axis
  • 'baseline' — Items align by their text baselines
stretch (default) Items stretch to fill height flex-start Items align to top flex-end Items align to bottom center Items vertically centered baseline A B C Text baselines align Perfect Centering justifyContent + alignItems: 'center'

The Perfect Center

One of the most common needs is centering something both horizontally and vertically. Use both properties together:

const styles = StyleSheet.create({
  // Center anything perfectly in its container
  perfectCenter: {
    flex: 1,
    justifyContent: 'center',  // Center on main axis
    alignItems: 'center',      // Center on cross axis
  },
});

// Usage: Loading screen
function LoadingScreen() {
  return (
    <View style={styles.perfectCenter}>
      <ActivityIndicator size="large" color="#2196F3" />
      <Text style={{ marginTop: 16 }}>Loading...</Text>
    </View>
  );
}

Example: Card with Image and Text

function ProductCard({ image, title, price }) {
  return (
    <View style={styles.card}>
      <Image source={{ uri: image }} style={styles.image} />
      <View style={styles.details}>
        <Text style={styles.title}>{title}</Text>
        <Text style={styles.price}>{price}</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    flexDirection: 'row',    // Image and details side by side
    alignItems: 'center',    // Vertically center the content
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 12,
    // Shadow
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  image: {
    width: 80,
    height: 80,
    borderRadius: 8,
  },
  details: {
    flex: 1,        // Take remaining horizontal space
    marginLeft: 12,
  },
  title: {
    fontSize: 16,
    fontWeight: '600',
    color: '#1a1a1a',
  },
  price: {
    fontSize: 14,
    color: '#4CAF50',
    marginTop: 4,
  },
});

alignSelf

While alignItems is set on the container and affects all children, alignSelf is set on individual items to override their container's alignment.

The values are the same as alignItems, plus 'auto' (use parent's alignItems):

  • 'auto' — Use parent's alignItems (default)
  • 'flex-start' — Align self to start
  • 'flex-end' — Align self to end
  • 'center' — Center self
  • 'stretch' — Stretch self
  • 'baseline' — Align to baseline
function AlignSelfDemo() {
  return (
    <View style={styles.container}>
      {/* Default - follows container's alignItems */}
      <View style={[styles.box, styles.box1]}>
        <Text style={styles.text}>auto</Text>
      </View>
      
      {/* Override - align to start */}
      <View style={[styles.box, styles.box2, { alignSelf: 'flex-start' }]}>
        <Text style={styles.text}>flex-start</Text>
      </View>
      
      {/* Override - center */}
      <View style={[styles.box, styles.box3, { alignSelf: 'center' }]}>
        <Text style={styles.text}>center</Text>
      </View>
      
      {/* Override - align to end */}
      <View style={[styles.box, styles.box4, { alignSelf: 'flex-end' }]}>
        <Text style={styles.text}>flex-end</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    // Default alignItems is stretch, but items override it
  },
  box: {
    padding: 16,
    marginVertical: 8,
  },
  box1: { backgroundColor: '#ef5350' },
  box2: { backgroundColor: '#42a5f5' },
  box3: { backgroundColor: '#66bb6a' },
  box4: { backgroundColor: '#ffca28' },
  text: { color: 'white', fontWeight: 'bold' },
});
auto (stretch) flex-start center flex-end Container width

Practical Use: Centering One Item

function MessageBubble({ text, isOwn }) {
  return (
    <View 
      style={[
        styles.bubble,
        // Own messages align right, others align left
        { alignSelf: isOwn ? 'flex-end' : 'flex-start' },
        isOwn ? styles.ownBubble : styles.otherBubble,
      ]}
    >
      <Text style={[styles.text, isOwn && styles.ownText]}>{text}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  bubble: {
    maxWidth: '75%',
    padding: 12,
    borderRadius: 16,
    marginVertical: 4,
  },
  ownBubble: {
    backgroundColor: '#2196F3',
    borderBottomRightRadius: 4,
  },
  otherBubble: {
    backgroundColor: '#e0e0e0',
    borderBottomLeftRadius: 4,
  },
  text: {
    fontSize: 16,
  },
  ownText: {
    color: 'white',
  },
});

flexWrap and gap

By default, flex items try to fit on one line. The flexWrap property controls what happens when items don't fit:

  • 'nowrap' — All items on one line, may overflow (default)
  • 'wrap' — Items wrap to next line when needed
  • 'wrap-reverse' — Items wrap in reverse order
function TagCloud({ tags }) {
  return (
    <View style={styles.container}>
      {tags.map((tag, index) => (
        <View key={index} style={styles.tag}>
          <Text style={styles.tagText}>{tag}</Text>
        </View>
      ))}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    flexWrap: 'wrap',    // Allow tags to wrap to next line
    gap: 8,              // Space between tags
  },
  tag: {
    backgroundColor: '#e3f2fd',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 16,
  },
  tagText: {
    color: '#1976D2',
    fontSize: 14,
  },
});
React TypeScript Expo iOS Android Flexbox StyleSheet Navigation Animation flexWrap: 'wrap' + gap: 8

The gap Property

The gap property adds consistent spacing between flex items. It's cleaner than using margin on each item:

const styles = StyleSheet.create({
  // Using gap (cleaner)
  withGap: {
    flexDirection: 'row',
    gap: 16,  // 16dp between all items
  },
  
  // Specific gap directions
  specificGaps: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    rowGap: 16,     // Vertical gap between rows
    columnGap: 8,   // Horizontal gap between columns
  },
  
  // Old way (using margins) - messier
  oldWay: {
    flexDirection: 'row',
  },
  oldWayItem: {
    marginRight: 16,  // But last item has extra margin!
  },
});

✅ Use gap Instead of Margins

gap only creates space between items, not on the outer edges. This eliminates the classic problem of "extra margin on the last item" that you'd get with marginRight on each item.

Nested Flex Containers

Real layouts aren't flat — they're hierarchies of flex containers. Each View can be both a flex item (child) and a flex container (parent).

flowchart TB
    subgraph Screen["Screen (flex: 1, column)"]
        direction TB
        subgraph Header["Header (row)"]
            Logo["Logo"]
            NavItems["Nav Items"]
        end
        subgraph Content["Content (flex: 1, column)"]
            subgraph Card["Card (row)"]
                CardImage["Image"]
                CardText["Text Stack"]
            end
        end
        subgraph Footer["Footer (row, space-between)"]
            Copyright["©"]
            Links["Links"]
        end
    end
    
    style Screen fill:#e3f2fd
    style Header fill:#fff3e0
    style Content fill:#e8f5e9
    style Footer fill:#fce4ec
    style Card fill:#f3e5f5
                

Example: Complete Screen Layout

function ProfileScreen() {
  return (
    <View style={styles.screen}>
      {/* Header Row */}
      <View style={styles.header}>
        <Pressable>
          <Text style={styles.backButton}>← Back</Text>
        </Pressable>
        <Text style={styles.headerTitle}>Profile</Text>
        <Pressable>
          <Text style={styles.editButton}>Edit</Text>
        </Pressable>
      </View>

      {/* Main Content - Scrollable */}
      <ScrollView style={styles.content}>
        {/* Profile Card */}
        <View style={styles.profileCard}>
          <Image source={{ uri: '...' }} style={styles.avatar} />
          <View style={styles.profileInfo}>
            <Text style={styles.name}>Jane Developer</Text>
            <Text style={styles.role}>React Native Engineer</Text>
          </View>
        </View>

        {/* Stats Row */}
        <View style={styles.statsRow}>
          <View style={styles.stat}>
            <Text style={styles.statNumber}>142</Text>
            <Text style={styles.statLabel}>Projects</Text>
          </View>
          <View style={styles.stat}>
            <Text style={styles.statNumber}>8.5k</Text>
            <Text style={styles.statLabel}>Followers</Text>
          </View>
          <View style={styles.stat}>
            <Text style={styles.statNumber}>284</Text>
            <Text style={styles.statLabel}>Following</Text>
          </View>
        </View>
      </ScrollView>

      {/* Bottom Tab Bar */}
      <View style={styles.tabBar}>
        <Pressable style={styles.tab}>
          <Text>🏠</Text>
        </Pressable>
        <Pressable style={styles.tab}>
          <Text>🔍</Text>
        </Pressable>
        <Pressable style={[styles.tab, styles.activeTab]}>
          <Text>👤</Text>
        </Pressable>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  // Screen container
  screen: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },

  // Header (horizontal)
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingHorizontal: 16,
    paddingVertical: 12,
    backgroundColor: '#fff',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  backButton: { color: '#2196F3', fontSize: 16 },
  headerTitle: { fontSize: 18, fontWeight: '600' },
  editButton: { color: '#2196F3', fontSize: 16 },

  // Main content
  content: {
    flex: 1,
    padding: 16,
  },

  // Profile card (horizontal)
  profileCard: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#fff',
    padding: 16,
    borderRadius: 12,
    marginBottom: 16,
  },
  avatar: {
    width: 80,
    height: 80,
    borderRadius: 40,
  },
  profileInfo: {
    marginLeft: 16,
    flex: 1,
  },
  name: { fontSize: 20, fontWeight: 'bold' },
  role: { fontSize: 14, color: '#666', marginTop: 4 },

  // Stats row (horizontal, evenly spaced)
  statsRow: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: '#fff',
    padding: 16,
    borderRadius: 12,
  },
  stat: {
    alignItems: 'center',
  },
  statNumber: { fontSize: 24, fontWeight: 'bold' },
  statLabel: { fontSize: 12, color: '#666', marginTop: 4 },

  // Tab bar (horizontal, evenly spaced)
  tabBar: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    paddingVertical: 12,
    backgroundColor: '#fff',
    borderTopWidth: 1,
    borderTopColor: '#e0e0e0',
  },
  tab: {
    padding: 8,
  },
  activeTab: {
    backgroundColor: '#e3f2fd',
    borderRadius: 8,
  },
});

💡 Nesting Strategy

Think in terms of "boxes within boxes." Start with the outermost container (usually flex: 1 for the screen), then identify horizontal vs vertical sections at each level. Add flex properties as needed to distribute space.

Common Layout Patterns

Here are the flex configurations you'll use repeatedly in mobile apps:

Pattern 1: Full Screen with Fixed Header/Footer

const styles = StyleSheet.create({
  screen: { flex: 1 },
  header: { height: 60 },         // Fixed
  content: { flex: 1 },           // Fills remaining space
  footer: { height: 50 },         // Fixed
});

Pattern 2: Horizontal Row with Push

// Logo left, buttons right
const styles = StyleSheet.create({
  row: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  logo: {
    // No flex - size to content
  },
  spacer: {
    flex: 1,  // Pushes everything after it to the right
  },
  buttons: {
    flexDirection: 'row',
    gap: 8,
  },
});

// <View style={styles.row}>
//   <Logo />
//   <View style={styles.spacer} />
//   <View style={styles.buttons}>...</View>
// </View>

Pattern 3: Equal Width Columns

const styles = StyleSheet.create({
  row: {
    flexDirection: 'row',
    gap: 12,
  },
  column: {
    flex: 1,  // Each column gets equal width
  },
});

Pattern 4: Card with Image Left, Content Right

const styles = StyleSheet.create({
  card: {
    flexDirection: 'row',
    alignItems: 'center',    // Vertically center
    padding: 12,
  },
  image: {
    width: 60,
    height: 60,
  },
  content: {
    flex: 1,                 // Take remaining width
    marginLeft: 12,
  },
});

Pattern 5: Centered Modal

const styles = StyleSheet.create({
  overlay: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'rgba(0,0,0,0.5)',
  },
  modal: {
    width: '80%',
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 20,
  },
});

Pattern 6: Grid of Items

const styles = StyleSheet.create({
  grid: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    gap: 12,
  },
  gridItem: {
    width: '48%',  // ~2 columns with gap
    // Or for 3 columns:
    // width: '31%',
    aspectRatio: 1,
    backgroundColor: '#f0f0f0',
    borderRadius: 8,
  },
});

Hands-On Exercises

Practice these exercises to solidify your Flexbox skills.

Exercise 1: Build a Navigation Header

Create a header with a back button on the left, title in the center, and a settings icon on the right.

Show Solution
function Header({ title }) {
  return (
    <View style={styles.header}>
      <Pressable style={styles.iconButton}>
        <Text style={styles.icon}>←</Text>
      </Pressable>
      
      <Text style={styles.title}>{title}</Text>
      
      <Pressable style={styles.iconButton}>
        <Text style={styles.icon}>⚙️</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    height: 56,
    paddingHorizontal: 16,
    backgroundColor: '#fff',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  iconButton: {
    width: 40,
    height: 40,
    justifyContent: 'center',
    alignItems: 'center',
  },
  icon: {
    fontSize: 20,
  },
  title: {
    fontSize: 18,
    fontWeight: '600',
    flex: 1,
    textAlign: 'center',
  },
});

Key insight: justifyContent: 'space-between' pushes the buttons to edges, but flex: 1 on the title with textAlign: 'center' ensures the title stays centered even if button widths differ.

Exercise 2: Create a Two-Column Stats Display

Build a row showing two statistics with equal widths, each displaying a number and label.

Show Solution
function StatsDisplay({ stats }) {
  return (
    <View style={styles.statsContainer}>
      {stats.map((stat, index) => (
        <View key={index} style={styles.statBox}>
          <Text style={styles.statValue}>{stat.value}</Text>
          <Text style={styles.statLabel}>{stat.label}</Text>
        </View>
      ))}
    </View>
  );
}

// Usage: <StatsDisplay stats={[{ value: '1.2k', label: 'Followers' }, { value: '342', label: 'Following' }]} />

const styles = StyleSheet.create({
  statsContainer: {
    flexDirection: 'row',
    gap: 16,
  },
  statBox: {
    flex: 1,               // Equal widths
    backgroundColor: '#f5f5f5',
    padding: 16,
    borderRadius: 12,
    alignItems: 'center',  // Center content horizontally
  },
  statValue: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#1a1a1a',
  },
  statLabel: {
    fontSize: 14,
    color: '#666',
    marginTop: 4,
  },
});

Exercise 3: Build a Chat Message Layout

Create a chat bubble that aligns left for received messages and right for sent messages. Include timestamp below the bubble.

Show Solution
interface MessageProps {
  text: string;
  time: string;
  isOwn: boolean;
}

function ChatMessage({ text, time, isOwn }: MessageProps) {
  return (
    <View style={[
      styles.messageContainer,
      { alignItems: isOwn ? 'flex-end' : 'flex-start' }
    ]}>
      <View style={[
        styles.bubble,
        isOwn ? styles.ownBubble : styles.otherBubble,
      ]}>
        <Text style={[
          styles.messageText,
          isOwn && styles.ownText,
        ]}>
          {text}
        </Text>
      </View>
      <Text style={styles.timestamp}>{time}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  messageContainer: {
    marginVertical: 4,
    paddingHorizontal: 12,
  },
  bubble: {
    maxWidth: '75%',
    padding: 12,
    borderRadius: 16,
  },
  ownBubble: {
    backgroundColor: '#2196F3',
    borderBottomRightRadius: 4,
  },
  otherBubble: {
    backgroundColor: '#e8e8e8',
    borderBottomLeftRadius: 4,
  },
  messageText: {
    fontSize: 16,
    color: '#1a1a1a',
  },
  ownText: {
    color: '#fff',
  },
  timestamp: {
    fontSize: 11,
    color: '#999',
    marginTop: 4,
  },
});

Key insight: The container uses alignItems to push the bubble and timestamp together to the appropriate side, while the bubble uses maxWidth: '75%' to prevent overly long messages.

Summary

You've now mastered Flexbox in React Native — the single most important layout skill you'll use every day.

🎯 Key Takeaways

  • Every View is a flex container — no need for display: 'flex'
  • flexDirection defaults to 'column' — unlike CSS which defaults to 'row'
  • justifyContent controls main axis alignment
  • alignItems controls cross axis alignment
  • flex: 1 makes items fill available space proportionally
  • gap is cleaner than margins for spacing
  • Nest containers to build complex layouts

Quick Reference Cheat Sheet

// Flexbox Cheat Sheet
const flexPatterns = StyleSheet.create({
  // Fill screen
  fillScreen: { flex: 1 },
  
  // Horizontal row
  row: { flexDirection: 'row' },
  
  // Center everything
  centerAll: { justifyContent: 'center', alignItems: 'center' },
  
  // Space items evenly
  spaceEvenly: { justifyContent: 'space-between' },
  
  // Equal width columns
  equalColumns: { flex: 1 },
  
  // Wrap items
  wrap: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 },
  
  // Push item to end
  pushRight: { marginLeft: 'auto' }, // or use flex: 1 spacer
});

// Remember:
// - flexDirection: 'column' is DEFAULT (not 'row')
// - Main axis = direction of flow
// - Cross axis = perpendicular
// - justifyContent → main axis
// - alignItems → cross axis
// - alignSelf → override for single item

Coming Up Next

In the next lesson, we'll explore Common Layout Patterns where we'll build complete, reusable layout components for headers, cards, forms, lists, and more. You'll see how Flexbox combines with other StyleSheet properties to create polished, production-ready interfaces.