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, andalignItemseffectively - 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:
- Flex Container: The parent element that holds and arranges children
- 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.
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 axisalignItems→ controls alignment on the 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: 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: 1on a container = "fill the entire screen"flex: 1on list items = "divide space equally"- No
flexon buttons = "size to fit the text" - Fixed
heighton 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
},
});
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
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
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' },
});
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,
},
});
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.