React Native Layout – Building Responsive Mobile UIs
React Native uses a flexbox-based layout system that's similar to CSS Flexbox but with mobile-specific considerations. Unlike web CSS, the default flexDirection is column (not row), and all dimensions are unitless (representing logical pixels). In 2026, mastering layout is essential for creating apps that work seamlessly across iPhones, iPads, Android devices, and foldables. This guide covers everything from basic flexbox to advanced responsive patterns.
- Flexbox Fundamentals
Flexbox is the primary layout system in React Native. Understanding these properties is critical for any layout task.
import React from 'react'; import { View, Text, StyleSheet, ScrollView } from 'react-native'; const FlexboxFundamentals = () => { return ( <ScrollView style={styles.container}> <Text style={styles.title}>Flexbox Fundamentals</Text> {/* 1. Flex Direction */} <Text style={styles.sectionTitle}>1. flexDirection: 'row' vs 'column'</Text> <View style={styles.rowContainer}> <View style={[styles.box, { backgroundColor: '#FF3B30' }]}><Text>1</Text></View> <View style={[styles.box, { backgroundColor: '#FF9500' }]}><Text>2</Text></View> <View style={[styles.box, { backgroundColor: '#FFCC00' }]}><Text>3</Text></View> </View> <View style={styles.columnContainer}> <View style={[styles.box, { backgroundColor: '#4CD964' }]}><Text>1</Text></View> <View style={[styles.box, { backgroundColor: '#007AFF' }]}><Text>2</Text></View> <View style={[styles.box, { backgroundColor: '#5856D6' }]}><Text>3</Text></View> </View> {/* 2. Justify Content */} <Text style={styles.sectionTitle}>2. justifyContent Options</Text> <Text style={styles.subsection}>flex-start (default)</Text> <View style={[styles.justifyRow, { justifyContent: 'flex-start' }]}> <View style={styles.smallBox} /><View style={styles.smallBox} /><View style={styles.smallBox} /> </View> <Text style={styles.subsection}>center</Text> <View style={[styles.justifyRow, { justifyContent: 'center' }]}> <View style={styles.smallBox} /><View style={styles.smallBox} /><View style={styles.smallBox} /> </View> <Text style={styles.subsection}>space-between</Text> <View style={[styles.justifyRow, { justifyContent: 'space-between' }]}> <View style={styles.smallBox} /><View style={styles.smallBox} /><View style={styles.smallBox} /> </View> <Text style={styles.subsection}>space-around</Text> <View style={[styles.justifyRow, { justifyContent: 'space-around' }]}> <View style={styles.smallBox} /><View style={styles.smallBox} /><View style={styles.smallBox} /> </View> <Text style={styles.subsection}>space-evenly</Text> <View style={[styles.justifyRow, { justifyContent: 'space-evenly' }]}> <View style={styles.smallBox} /><View style={styles.smallBox} /><View style={styles.smallBox} /> </View> {/* 3. Align Items */} <Text style={styles.sectionTitle}>3. alignItems Options</Text> <Text style={styles.subsection}>flex-start</Text> <View style={[styles.alignRow, { alignItems: 'flex-start' }]}> <View style={[styles.variableBox, { height: 40 }]} /> <View style={[styles.variableBox, { height: 60 }]} /> <View style={[styles.variableBox, { height: 80 }]} /> </View> <Text style={styles.subsection}>center</Text> <View style={[styles.alignRow, { alignItems: 'center' }]}> <View style={[styles.variableBox, { height: 40 }]} /> <View style={[styles.variableBox, { height: 60 }]} /> <View style={[styles.variableBox, { height: 80 }]} /> </View> <Text style={styles.subsection}>flex-end</Text> <View style={[styles.alignRow, { alignItems: 'flex-end' }]}> <View style={[styles.variableBox, { height: 40 }]} /> <View style={[styles.variableBox, { height: 60 }]} /> <View style={[styles.variableBox, { height: 80 }]} /> </View> <Text style={styles.subsection}>stretch</Text> <View style={[styles.alignRow, { alignItems: 'stretch' }]}> <View style={[styles.variableBox, { width: 'auto' }]} /> <View style={[styles.variableBox, { width: 'auto' }]} /> <View style={[styles.variableBox, { width: 'auto' }]} /> </View> {/* 4. Flex Wrap */} <Text style={styles.sectionTitle}>4. flexWrap: 'wrap'</Text> <View style={styles.wrapContainer}> {[...Array(10)].map((_, i) => ( <View key={i} style={[styles.wrapBox, { backgroundColor: '#34C759' }]}> <Text style={styles.wrapText}>{i + 1}</Text> </View> ))} </View> {/* 5. Flex Grow & Shrink */} <Text style={styles.sectionTitle}>5. flex: 1 (Grow to fill space)</Text> <View style={styles.flexContainer}> <View style={[styles.fixedBox, { backgroundColor: '#FF3B30', width: 80 }]}> <Text>Fixed</Text> </View> <View style={[styles.growBox, { backgroundColor: '#007AFF', flex: 1 }]}> <Text>Grows (flex:1)</Text> </View> <View style={[styles.growBox, { backgroundColor: '#34C759', flex: 2 }]}> <Text>Grows More (flex:2)</Text> </View> </View> {/* 6. Align Self */} <Text style={styles.sectionTitle}>6. alignSelf (override parent)</Text> <View style={styles.alignSelfContainer}> <View style={[styles.alignSelfBox, { alignSelf: 'flex-start', backgroundColor: '#FF3B30' }]}> <Text>flex-start</Text> </View> <View style={[styles.alignSelfBox, { alignSelf: 'center', backgroundColor: '#007AFF' }]}> <Text>center</Text> </View> <View style={[styles.alignSelfBox, { alignSelf: 'flex-end', backgroundColor: '#34C759' }]}> <Text>flex-end</Text> </View> </View> </ScrollView> ); }; const styles = StyleSheet.create({ container: { flex: 1, padding: 16, backgroundColor: '#fff' }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20 }, sectionTitle: { fontSize: 18, fontWeight: '600', marginTop: 20, marginBottom: 12 }, subsection: { fontSize: 14, fontWeight: '500', marginTop: 8, marginBottom: 4, color: '#666' }, rowContainer: { flexDirection: 'row', justifyContent: 'space-around', backgroundColor: '#f0f0f0', padding: 10, borderRadius: 8 }, columnContainer: { flexDirection: 'column', alignItems: 'center', backgroundColor: '#f0f0f0', padding: 10, borderRadius: 8, gap: 10 }, box: { width: 60, height: 60, justifyContent: 'center', alignItems: 'center', borderRadius: 8 }, smallBox: { width: 50, height: 50, backgroundColor: '#007AFF', borderRadius: 8 }, justifyRow: { flexDirection: 'row', backgroundColor: '#f0f0f0', padding: 10, borderRadius: 8, marginBottom: 8 }, alignRow: { flexDirection: 'row', backgroundColor: '#f0f0f0', padding: 10, borderRadius: 8, height: 120, marginBottom: 8, gap: 8 }, variableBox: { width: 60, backgroundColor: '#007AFF', borderRadius: 8 }, wrapContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, backgroundColor: '#f0f0f0', padding: 10, borderRadius: 8 }, wrapBox: { width: 60, height: 60, justifyContent: 'center', alignItems: 'center', borderRadius: 8 }, wrapText: { color: '#fff', fontWeight: 'bold' }, flexContainer: { flexDirection: 'row', backgroundColor: '#f0f0f0', padding: 10, borderRadius: 8, height: 80, gap: 8 }, fixedBox: { justifyContent: 'center', alignItems: 'center', borderRadius: 8 }, growBox: { justifyContent: 'center', alignItems: 'center', borderRadius: 8 }, alignSelfContainer: { flexDirection: 'row', justifyContent: 'space-around', backgroundColor: '#f0f0f0', padding: 20, borderRadius: 8, height: 150 }, alignSelfBox: { width: 70, height: 50, justifyContent: 'center', alignItems: 'center', borderRadius: 8 } }); export default FlexboxFundamentals; // Flexbox Property Reference: // // Container Properties: // - flexDirection: 'row' | 'column' | 'row-reverse' | 'column-reverse' (default: 'column') // - justifyContent: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' // - alignItems: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline' // - flexWrap: 'wrap' | 'nowrap' | 'wrap-reverse' // - alignContent: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'space-between' | 'space-around' // - gap: number (spacing between children) // - rowGap: number // - columnGap: number // // Item Properties: // - flex: number (0 = no grow, positive = grow proportionally) // - flexGrow: number // - flexShrink: number // - flexBasis: number | 'auto' // - alignSelf: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline' // - aspectRatio: number
- Positioning (Relative vs Absolute)
React Native supports relative (default) and absolute positioning. Absolute positioning is useful for overlays, badges, headers, and custom layouts.
import React, { useState } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Image } from 'react-native'; const PositioningExamples = () => { const [showOverlay, setShowOverlay] = useState(false); return ( <ScrollView style={styles.container}> <Text style={styles.title}>Positioning: Relative vs Absolute</Text> {/* 1. Relative Positioning (default) */} <Text style={styles.sectionTitle}>1. Relative Positioning (Default)</Text> <View style={styles.relativeContainer}> <View style={[styles.item, { backgroundColor: '#FF3B30' }]}> <Text>Item 1</Text> </View> <View style={[styles.item, { backgroundColor: '#007AFF' }]}> <Text>Item 2</Text> </View> <View style={[styles.item, { backgroundColor: '#34C759' }]}> <Text>Item 3</Text> </View> </View> {/* 2. Relative with offset */} <Text style={styles.sectionTitle}>2. Relative with top/left offset</Text> <View style={styles.offsetContainer}> <View style={[styles.offsetItem, { backgroundColor: '#FF3B30' }]}> <Text>Normal</Text> </View> <View style={[styles.offsetItem, { backgroundColor: '#007AFF', position: 'relative', top: 20, left: 20 }]}> <Text>Offset 20,20</Text> </View> <View style={[styles.offsetItem, { backgroundColor: '#34C759' }]}> <Text>Normal</Text> </View> </View> {/* 3. Absolute Positioning - Badge Example */} <Text style={styles.sectionTitle}>3. Absolute Positioning - Badge</Text> <View style={styles.badgeContainer}> <View style={styles.iconContainer}> <Text style={styles.iconText}>📧</Text> <View style={styles.badge}> <Text style={styles.badgeText}>3</Text> </View> </View> <Text>Notifications Icon with Badge</Text> </View> {/* 4. Absolute Positioning - Overlay */} <Text style={styles.sectionTitle}>4. Absolute Positioning - Image Overlay</Text> <TouchableOpacity style={styles.imageContainer} onPress={() => setShowOverlay(!showOverlay)} > <Image source={{ uri: 'https://picsum.photos/300/200' }} style={styles.image} /> {showOverlay && ( <View style={styles.imageOverlay}> <Text style={styles.overlayText}>Tap to close</Text> </View> )} <View style={styles.imageLabel}> <Text style={styles.labelText}>Tap for overlay</Text> </View> </TouchableOpacity> {/* 5. Absolute Positioning - Centered Modal */} <Text style={styles.sectionTitle}>5. Absolute Centered Modal</Text> <View style={styles.modalTriggerContainer}> <TouchableOpacity style={styles.modalButton} onPress={() => setShowOverlay(true)} > <Text style={styles.buttonText}>Show Modal</Text> </TouchableOpacity> </View> {showOverlay && ( <TouchableOpacity style={styles.modalOverlay} activeOpacity={1} onPress={() => setShowOverlay(false)} > <View style={styles.modalContent}> <Text style={styles.modalTitle}>Absolute Modal</Text> <Text style={styles.modalText}>This modal is positioned absolutely with centered content.</Text> <TouchableOpacity style={styles.closeButton} onPress={() => setShowOverlay(false)} > <Text style={styles.closeButtonText}>Close</Text> </TouchableOpacity> </View> </TouchableOpacity> )} {/* 6. Z-Index Layering */} <Text style={styles.sectionTitle}>6. Z-Index Layering</Text> <View style={styles.zIndexContainer}> <View style={[styles.zIndexBox, { backgroundColor: '#FF3B30', zIndex: 3, top: 0, left: 0 }]}> <Text>z-index:3</Text> </View> <View style={[styles.zIndexBox, { backgroundColor: '#007AFF', zIndex: 2, top: -30, left: 30 }]}> <Text>z-index:2</Text> </View> <View style={[styles.zIndexBox, { backgroundColor: '#34C759', zIndex: 1, top: -60, left: 60 }]}> <Text>z-index:1</Text> </View> </View> {/* 7. Sticky Header Pattern */} <Text style={styles.sectionTitle}>7. Sticky Header (using position: 'sticky')</Text> <View style={styles.stickyContainer}> <View style={styles.stickyHeader}> <Text style={styles.stickyHeaderText}>This header sticks on scroll</Text> </View> {[...Array(10)].map((_, i) => ( <View key={i} style={styles.stickyItem}> <Text>Scrollable content {i + 1}</Text> </View> ))} </View> </ScrollView> ); }; const styles = StyleSheet.create({ container: { flex: 1, padding: 16, backgroundColor: '#fff' }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20 }, sectionTitle: { fontSize: 18, fontWeight: '600', marginTop: 20, marginBottom: 12 }, // Relative positioning relativeContainer: { flexDirection: 'row', justifyContent: 'space-around', backgroundColor: '#f0f0f0', padding: 10, borderRadius: 8 }, item: { width: 60, height: 60, justifyContent: 'center', alignItems: 'center', borderRadius: 8 }, // Offset positioning offsetContainer: { flexDirection: 'row', justifyContent: 'space-around', backgroundColor: '#f0f0f0', padding: 20, borderRadius: 8, minHeight: 120 }, offsetItem: { width: 60, height: 60, justifyContent: 'center', alignItems: 'center', borderRadius: 8 }, // Badge example badgeContainer: { alignItems: 'center', padding: 20, backgroundColor: '#f0f0f0', borderRadius: 8 }, iconContainer: { position: 'relative', width: 50, height: 50, justifyContent: 'center', alignItems: 'center' }, iconText: { fontSize: 40 }, badge: { position: 'absolute', top: -5, right: -10, backgroundColor: '#FF3B30', borderRadius: 10, minWidth: 20, height: 20, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 4 }, badgeText: { color: '#fff', fontSize: 12, fontWeight: 'bold' }, // Image overlay imageContainer: { position: 'relative', borderRadius: 12, overflow: 'hidden' }, image: { width: '100%', height: 200, borderRadius: 12 }, imageOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.7)', justifyContent: 'center', alignItems: 'center' }, imageLabel: { position: 'absolute', bottom: 10, left: 10, backgroundColor: 'rgba(0,0,0,0.6)', paddingHorizontal: 12, paddingVertical: 4, borderRadius: 20 }, labelText: { color: '#fff', fontSize: 12 }, overlayText: { color: '#fff', fontSize: 18, fontWeight: 'bold' }, // Modal modalTriggerContainer: { alignItems: 'center', padding: 10 }, modalButton: { backgroundColor: '#007AFF', paddingHorizontal: 20, paddingVertical: 12, borderRadius: 8 }, buttonText: { color: '#fff', fontWeight: '600' }, modalOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'center', alignItems: 'center' }, modalContent: { backgroundColor: '#fff', borderRadius: 12, padding: 20, width: '80%', alignItems: 'center' }, modalTitle: { fontSize: 20, fontWeight: 'bold', marginBottom: 12 }, modalText: { textAlign: 'center', marginBottom: 20 }, closeButton: { backgroundColor: '#007AFF', paddingHorizontal: 20, paddingVertical: 10, borderRadius: 8 }, closeButtonText: { color: '#fff', fontWeight: '600' }, // Z-index zIndexContainer: { height: 100, position: 'relative', marginBottom: 20 }, zIndexBox: { position: 'absolute', width: 80, height: 80, justifyContent: 'center', alignItems: 'center', borderRadius: 8 }, // Sticky header stickyContainer: { maxHeight: 300, backgroundColor: '#f0f0f0', borderRadius: 8, overflow: 'hidden' }, stickyHeader: { position: 'sticky', top: 0, backgroundColor: '#007AFF', padding: 12, zIndex: 1 }, stickyHeaderText: { color: '#fff', fontWeight: 'bold', textAlign: 'center' }, stickyItem: { padding: 16, borderBottomWidth: 1, borderBottomColor: '#e0e0e0', backgroundColor: '#fff' } }); export default PositioningExamples; // Positioning Reference: // // position: 'relative' (default) // - Element positioned relative to its normal flow position // - top, bottom, left, right offset from normal position // // position: 'absolute' // - Element positioned relative to its nearest positioned ancestor // - Removed from normal document flow // - Other elements ignore absolute positioned elements // - Use zIndex to control stacking order // // position: 'sticky' (iOS only, limited support) // - Behaves as relative until scroll threshold, then fixed // // zIndex: number // - Controls stacking order (higher number = on top) // - Only works on positioned elements (relative/absolute)
- Responsive Layout Patterns
Create layouts that adapt to different screen sizes, orientations, and devices using responsive techniques.
import React from 'react'; import { View, Text, StyleSheet, ScrollView, useWindowDimensions, Platform } from 'react-native'; const ResponsiveLayouts = () => { const { width, height } = useWindowDimensions(); const isLandscape = width > height; const isTablet = width >= 768; const isSmallDevice = width <= 375; // Responsive grid columns const getGridColumns = () => { if (isTablet) return 4; if (isLandscape) return 3; return 2; }; const numColumns = getGridColumns(); const gap = 12; const itemWidth = (width - (gap * (numColumns + 1))) / numColumns; // Responsive font sizes const getFontSize = () => { if (isTablet) return 18; if (isSmallDevice) return 12; return 14; }; return ( <ScrollView style={styles.container}> <Text style={styles.title}>Responsive Layout Patterns</Text> {/* Device Info Card */} <View style={styles.infoCard}> <Text style={styles.infoText}>Width: {Math.round(width)}px</Text> <Text style={styles.infoText}>Height: {Math.round(height)}px</Text> <Text style={styles.infoText}>Orientation: {isLandscape ? 'Landscape' : 'Portrait'}</Text> <Text style={styles.infoText}>Device: {isTablet ? 'Tablet' : 'Phone'}</Text> <Text style={styles.infoText}>Platform: {Platform.OS}</Text> </View> {/* Responsive Grid */} <Text style={styles.sectionTitle}>Responsive Grid ({numColumns} columns)</Text> <View style={styles.gridContainer}> {[...Array(8)].map((_, i) => ( <View key={i} style={[ styles.gridItem, { width: itemWidth, height: itemWidth, backgroundColor: `hsl(${i * 45}, 70%, 50%)` } ]} > <Text style={styles.gridText}>{i + 1}</Text> </View> ))} </View> {/* Responsive Layout: Column vs Row */} <Text style={styles.sectionTitle}>Responsive Layout (Column/Row)</Text> {isTablet ? ( <View style={styles.twoColumnLayout}> <View style={styles.column}> <Text style={styles.columnTitle}>Column 1</Text> <Text>Sidebar content for tablets</Text> </View> <View style={styles.column}> <Text style={styles.columnTitle}>Column 2</Text> <Text>Main content area</Text> </View> </View> ) : ( <View style={styles.singleColumnLayout}> <Text style={styles.columnTitle}>Single Column (Phone)</Text> <Text>Stacked vertically on phones</Text> </View> )} {/* Responsive Cards */} <Text style={styles.sectionTitle}>Responsive Cards</Text> <View style={styles.cardsContainer}> {[1, 2, 3].map((i) => ( <View key={i} style={[ styles.card, isTablet && styles.cardTablet, isSmallDevice && styles.cardSmall ]} > <Text style={[styles.cardTitle, { fontSize: getFontSize() + 2 }]}> Card {i} </Text> <Text style={[styles.cardText, { fontSize: getFontSize() }]}> Responsive card that adapts to screen size. </Text> </View> ))} </View> {/* Percentage-based widths */} <Text style={styles.sectionTitle}>Percentage Widths</Text> <View style={styles.percentageContainer}> <View style={[styles.percentageBox, { width: '25%', backgroundColor: '#FF3B30' }]}> <Text style={styles.percentageText}>25%</Text> </View> <View style={[styles.percentageBox, { width: '50%', backgroundColor: '#007AFF' }]}> <Text style={styles.percentageText}>50%</Text> </View> <View style={[styles.percentageBox, { width: '25%', backgroundColor: '#34C759' }]}> <Text style={styles.percentageText}>25%</Text> </View> </View> {/* Dynamic spacing */} <Text style={styles.sectionTitle}>Dynamic Spacing</Text> <View style={[styles.dynamicSpacing, { padding: isTablet ? 24 : 16 }]}> <Text>Spacing adapts to screen size</Text> <View style={[styles.spacingBox, { marginTop: isTablet ? 20 : 12 }]} /> <Text>Tablets get more breathing room</Text> </View> {/* Orientation-specific layout */} <Text style={styles.sectionTitle}>Orientation-Specific</Text> {isLandscape ? ( <View style={styles.landscapeLayout}> <View style={styles.landscapeSidebar}> <Text>Sidebar</Text> </View> <View style={styles.landscapeContent}> <Text>Main Content (Landscape Optimized)</Text> </View> </View> ) : ( <View style={styles.portraitLayout}> <View style={styles.portraitHeader}> <Text>Header (Portrait)</Text> </View> <View style={styles.portraitContent}> <Text>Scrollable Content</Text> </View> </View> )} </ScrollView> ); }; const styles = StyleSheet.create({ container: { flex: 1, padding: 16, backgroundColor: '#fff' }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20 }, sectionTitle: { fontSize: 18, fontWeight: '600', marginTop: 20, marginBottom: 12 }, infoCard: { backgroundColor: '#f0f0f0', padding: 16, borderRadius: 12, gap: 4 }, infoText: { fontSize: 14, color: '#333' }, gridContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: 12 }, gridItem: { borderRadius: 8, justifyContent: 'center', alignItems: 'center' }, gridText: { color: '#fff', fontWeight: 'bold', fontSize: 18 }, twoColumnLayout: { flexDirection: 'row', gap: 16 }, column: { flex: 1, backgroundColor: '#f0f0f0', padding: 16, borderRadius: 8 }, columnTitle: { fontWeight: 'bold', marginBottom: 8 }, singleColumnLayout: { backgroundColor: '#f0f0f0', padding: 16, borderRadius: 8 }, cardsContainer: { gap: 12 }, card: { backgroundColor: '#f9f9f9', padding: 16, borderRadius: 12, borderWidth: 1, borderColor: '#e0e0e0' }, cardTablet: { padding: 20 }, cardSmall: { padding: 12 }, cardTitle: { fontWeight: 'bold', marginBottom: 8 }, cardText: { color: '#666' }, percentageContainer: { flexDirection: 'row', height: 60, borderRadius: 8, overflow: 'hidden' }, percentageBox: { justifyContent: 'center', alignItems: 'center' }, percentageText: { color: '#fff', fontWeight: 'bold' }, dynamicSpacing: { backgroundColor: '#f0f0f0', borderRadius: 8, alignItems: 'center' }, spacingBox: { width: 50, height: 50, backgroundColor: '#007AFF', borderRadius: 8 }, landscapeLayout: { flexDirection: 'row', height: 150, gap: 12 }, landscapeSidebar: { width: '30%', backgroundColor: '#f0f0f0', borderRadius: 8, justifyContent: 'center', alignItems: 'center' }, landscapeContent: { flex: 1, backgroundColor: '#f0f0f0', borderRadius: 8, justifyContent: 'center', alignItems: 'center' }, portraitLayout: { height: 150, gap: 8 }, portraitHeader: { height: '30%', backgroundColor: '#f0f0f0', borderRadius: 8, justifyContent: 'center', alignItems: 'center' }, portraitContent: { flex: 1, backgroundColor: '#f0f0f0', borderRadius: 8, justifyContent: 'center', alignItems: 'center' } }); export default ResponsiveLayouts;
- Safe Area & Edge Handling
Handle notches, dynamic islands, and home indicators using SafeAreaView and SafeAreaProvider. This is critical for modern iPhones and Android devices with cutouts.
import React from 'react'; import { View, Text, StyleSheet, ScrollView, Platform, StatusBar } from 'react-native'; import { SafeAreaView, SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'; // Method 1: Using SafeAreaView (wraps content automatically) const BasicSafeAreaExample = () => { return ( <SafeAreaView style={styles.safeArea}> <View style={styles.content}> <Text style={styles.title}>SafeAreaView Example</Text> <Text>This content automatically avoids notches and home indicators</Text> </View> </SafeAreaView> ); }; // Method 2: Using useSafeAreaInsets hook for custom control const CustomInsetsExample = () => { const insets = useSafeAreaInsets(); return ( <View style={[ styles.container, { paddingTop: insets.top, paddingBottom: insets.bottom, paddingLeft: insets.left, paddingRight: insets.right } ]}> <View style={styles.header}> <Text>Custom Header (respects safe area)</Text> </View> <ScrollView style={styles.scrollContent}> <Text>Safe area insets:</Text> <Text>Top: {insets.top}</Text> <Text>Bottom: {insets.bottom}</Text> <Text>Left: {insets.left}</Text> <Text>Right: {insets.right}</Text> </ScrollView> <View style={[styles.footer, { paddingBottom: insets.bottom }]}> <Text>Footer respects bottom safe area</Text> </View> </View> ); }; // Method 3: Platform-specific status bar handling const StatusBarExample = () => { const insets = useSafeAreaInsets(); return ( <View style={styles.container}> {/* Status bar spacing */} <View style={{ height: Platform.OS === 'ios' ? insets.top : StatusBar.currentHeight }} /> <View style={styles.header}> <Text>Status Bar Spacing Applied</Text> </View> <ScrollView style={styles.content}> <Text>Content that scrolls below the header</Text> </ScrollView> </View> ); }; // Complete app with SafeAreaProvider const AppWithSafeArea = () => { return ( <SafeAreaProvider> <SafeAreaView style={styles.fullScreen}> <TabNavigator /> </SafeAreaView> </SafeAreaProvider> ); }; // Tab bar with safe area const TabNavigator = () => { const insets = useSafeAreaInsets(); return ( <View style={styles.tabContainer}> <View style={styles.tabContent}> <Text>Main Content</Text> </View> <View style={[styles.tabBar, { paddingBottom: insets.bottom || 10 }]}> <Text>Tab 1</Text> <Text>Tab 2</Text> <Text>Tab 3</Text> </View> </View> ); }; // Handling different device types const DeviceAwareLayout = () => { const insets = useSafeAreaInsets(); const hasNotch = insets.top > 20; const hasDynamicIsland = insets.top > 50; // Approximate return ( <SafeAreaView style={styles.safeArea}> <View style={[ styles.header, hasNotch && styles.headerWithNotch, hasDynamicIsland && styles.headerWithDynamicIsland ]}> <Text style={styles.headerText}> {hasDynamicIsland ? 'Dynamic Island Device' : hasNotch ? 'Notch Device' : 'Standard Device'} </Text> </View> <ScrollView contentContainerStyle={styles.scrollContent}> <Text>Content adjusts based on device type</Text> <View style={styles.infoCard}> <Text>Safe Area Top: {insets.top}</Text> <Text>Safe Area Bottom: {insets.bottom}</Text> <Text>Has Notch: {hasNotch ? 'Yes' : 'No'}</Text> <Text>Has Dynamic Island: {hasDynamicIsland ? 'Yes' : 'No'}</Text> </View> </ScrollView> </SafeAreaView> ); }; const styles = StyleSheet.create({ fullScreen: { flex: 1 }, safeArea: { flex: 1, backgroundColor: '#fff' }, container: { flex: 1, backgroundColor: '#fff' }, content: { flex: 1, padding: 16 }, title: { fontSize: 20, fontWeight: 'bold', marginBottom: 12 }, header: { backgroundColor: '#007AFF', padding: 16, alignItems: 'center' }, headerWithNotch: { paddingTop: 32 }, headerWithDynamicIsland: { paddingTop: 54 }, headerText: { color: '#fff', fontWeight: 'bold' }, footer: { backgroundColor: '#f0f0f0', padding: 16, alignItems: 'center' }, scrollContent: { padding: 16 }, infoCard: { backgroundColor: '#f0f0f0', padding: 16, borderRadius: 8, marginTop: 16, gap: 8 }, tabContainer: { flex: 1 }, tabContent: { flex: 1, justifyContent: 'center', alignItems: 'center' }, tabBar: { flexDirection: 'row', justifyContent: 'space-around', backgroundColor: '#f0f0f0', paddingTop: 10, paddingHorizontal: 16 } }); export { BasicSafeAreaExample, CustomInsetsExample, DeviceAwareLayout, AppWithSafeArea }; // Installation for react-native-safe-area-context: // npm install react-native-safe-area-context // cd ios && pod install // Usage: Wrap your root component with SafeAreaProvider
- Scroll View Layout Patterns
Master ScrollView layout patterns including nested scrolling, keyboard avoidance, and pull-to-refresh.
import React, { useRef } from 'react'; import { View, Text, StyleSheet, ScrollView, TextInput, TouchableOpacity, RefreshControl, KeyboardAvoidingView, Platform, Dimensions } from 'react-native'; const ScrollViewPatterns = () => { const [refreshing, setRefreshing] = React.useState(false); const scrollViewRef = useRef(null); // Pull to refresh const onRefresh = React.useCallback(() => { setRefreshing(true); setTimeout(() => { setRefreshing(false); }, 2000); }, []); // Scroll to top const scrollToTop = () => { scrollViewRef.current?.scrollTo({ y: 0, animated: true }); }; // Scroll to specific position const scrollToPosition = (y: number) => { scrollViewRef.current?.scrollTo({ y, animated: true }); }; return ( <KeyboardAvoidingView style={styles.container} behavior={Platform.OS === 'ios' ? 'padding' : 'height'} keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 20} > {/* Basic ScrollView with refresh control */} <ScrollView ref={scrollViewRef} showsVerticalScrollIndicator={true} showsHorizontalScrollIndicator={false} refreshControl={ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} /> } contentContainerStyle={styles.scrollContent} // Keyboard behavior keyboardDismissMode="interactive" keyboardShouldPersistTaps="handled" > <Text style={styles.title}>ScrollView Patterns</Text> {/* Horizontal ScrollView */} <Text style={styles.sectionTitle}>Horizontal Scrolling</Text> <ScrollView horizontal={true} showsHorizontalScrollIndicator={false} style={styles.horizontalScroll} contentContainerStyle={styles.horizontalContent} > {[...Array(10)].map((_, i) => ( <View key={i} style={[styles.horizontalItem, { backgroundColor: `hsl(${i * 36}, 70%, 50%)` }]}> <Text style={styles.horizontalText}>{i + 1}</Text> </View> ))} </ScrollView> {/* Nested ScrollViews (requires nestedScrollEnabled on Android) */} <Text style={styles.sectionTitle}>Nested Scrolling</Text> <View style={styles.nestedContainer}> <ScrollView nestedScrollEnabled={Platform.OS === 'android'} style={styles.nestedScroll} showsVerticalScrollIndicator={false} > {[...Array(5)].map((_, i) => ( <View key={i} style={styles.nestedItem}> <Text>Nested Content {i + 1}</Text> </View> ))} </ScrollView> </View> {/* Form with keyboard avoidance */} <Text style={styles.sectionTitle}>Form with Keyboard Avoidance</Text> <View style={styles.form}> <TextInput style={styles.input} placeholder="Name" /> <TextInput style={styles.input} placeholder="Email" keyboardType="email-address" /> <TextInput style={[styles.input, styles.textArea]} placeholder="Message" multiline numberOfLines={4} /> <TouchableOpacity style={styles.submitButton}> <Text style={styles.submitText}>Submit</Text> </TouchableOpacity> </View> {/* Sticky headers in ScrollView */} <Text style={styles.sectionTitle}>Sticky Elements</Text> <View style={styles.stickyContainer}> <View style={styles.stickyHeader}> <Text style={styles.stickyText}>This header sticks while scrolling</Text> </View> {[...Array(15)].map((_, i) => ( <View key={i} style={styles.stickyItem}> <Text>Scrollable content item {i + 1}</Text> </View> ))} </View> {/* Scroll to top button */} <TouchableOpacity style={styles.scrollTopButton} onPress={scrollToTop}> <Text style={styles.scrollTopText}>↑ Scroll to Top</Text> </TouchableOpacity> {/* Content footer */} <View style={styles.footer}> <Text>End of Content</Text> </View> </ScrollView> </KeyboardAvoidingView> ); }; // Advanced: Keyboard avoiding with custom insets const KeyboardAvoidingForm = () => { const [keyboardVisible, setKeyboardVisible] = React.useState(false); React.useEffect(() => { const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => { setKeyboardVisible(true); }); const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { setKeyboardVisible(false); }); return () => { keyboardDidShowListener.remove(); keyboardDidHideListener.remove(); }; }, []); return ( <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} style={styles.formContainer} keyboardVerticalOffset={keyboardVisible ? 0 : 100} > <ScrollView keyboardShouldPersistTaps="handled"> <Text style={styles.formTitle}>Contact Form</Text> {[...Array(8)].map((_, i) => ( <TextInput key={i} style={styles.formInput} placeholder={`Field ${i + 1}`} /> ))} <TouchableOpacity style={styles.formButton}> <Text style={styles.formButtonText}>Submit</Text> </TouchableOpacity> </ScrollView> </KeyboardAvoidingView> ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff' }, scrollContent: { padding: 16 }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20 }, sectionTitle: { fontSize: 18, fontWeight: '600', marginTop: 20, marginBottom: 12 }, horizontalScroll: { maxHeight: 100 }, horizontalContent: { gap: 12 }, horizontalItem: { width: 80, height: 80, borderRadius: 8, justifyContent: 'center', alignItems: 'center' }, horizontalText: { color: '#fff', fontWeight: 'bold', fontSize: 18 }, nestedContainer: { height: 150, borderWidth: 1, borderColor: '#e0e0e0', borderRadius: 8 }, nestedScroll: { flex: 1 }, nestedItem: { padding: 12, borderBottomWidth: 1, borderBottomColor: '#f0f0f0' }, form: { gap: 12 }, input: { borderWidth: 1, borderColor: '#e0e0e0', borderRadius: 8, padding: 12, fontSize: 16 }, textArea: { height: 100, textAlignVertical: 'top' }, submitButton: { backgroundColor: '#007AFF', padding: 16, borderRadius: 8, alignItems: 'center' }, submitText: { color: '#fff', fontWeight: '600', fontSize: 16 }, stickyContainer: { maxHeight: 300, borderWidth: 1, borderColor: '#e0e0e0', borderRadius: 8, overflow: 'hidden' }, stickyHeader: { position: 'sticky', top: 0, backgroundColor: '#007AFF', padding: 12, zIndex: 1 }, stickyText: { color: '#fff', fontWeight: 'bold', textAlign: 'center' }, stickyItem: { padding: 16, borderBottomWidth: 1, borderBottomColor: '#f0f0f0' }, scrollTopButton: { marginTop: 20, backgroundColor: '#f0f0f0', padding: 12, borderRadius: 8, alignItems: 'center' }, scrollTopText: { color: '#007AFF', fontWeight: '600' }, footer: { marginTop: 20, padding: 20, alignItems: 'center' }, formContainer: { flex: 1, backgroundColor: '#fff', padding: 16 }, formTitle: { fontSize: 20, fontWeight: 'bold', marginBottom: 20 }, formInput: { borderWidth: 1, borderColor: '#e0e0e0', borderRadius: 8, padding: 12, marginBottom: 12, fontSize: 16 }, formButton: { backgroundColor: '#007AFF', padding: 16, borderRadius: 8, alignItems: 'center', marginTop: 20 }, formButtonText: { color: '#fff', fontWeight: '600', fontSize: 16 } }); export default ScrollViewPatterns;
- Advanced Layout Techniques
Master advanced layout patterns including masonry grids, circular layouts, and custom measure functions.
import React from 'react'; import { View, Text, StyleSheet, ScrollView, Dimensions, Platform } from 'react-native'; const AdvancedLayouts = () => { const { width } = Dimensions.get('window'); const isTablet = width >= 768; // Masonry grid (Pinterest-style) const renderMasonryGrid = () => { const columnCount = isTablet ? 3 : 2; const columnWidth = (width - 48) / columnCount; // Random heights for masonry effect const items = [...Array(12)].map((_, i) => ({ id: i, height: 100 + (i % 5) * 30, color: `hsl(${i * 30}, 70%, 60%)` })); // Distribute items into columns const columns = Array(columnCount).fill().map(() => []); items.forEach((item, index) => { const columnIndex = index % columnCount; columns[columnIndex].push(item); }); return ( <View style={styles.masonryContainer}> {columns.map((column, colIndex) => ( <View key={colIndex} style={[styles.masonryColumn, { width: columnWidth }]}> {column.map((item) => ( <View key={item.id} style={[ styles.masonryItem, { height: item.height, backgroundColor: item.color } ]} > <Text style={styles.masonryText}>{item.id + 1}</Text> </View> ))} </View> ))} </View> ); }; // Circular layout const renderCircularLayout = () => { const centerX = width / 2; const centerY = 150; const radius = 100; const items = 8; return ( <View style={styles.circularContainer}> {[...Array(items)].map((_, i) => { const angle = (i * 360 / items) * Math.PI / 180; const x = centerX + radius * Math.cos(angle) - 20; const y = centerY + radius * Math.sin(angle) - 20; return ( <View key={i} style={[ styles.circularItem, { position: 'absolute', left: x, top: y, backgroundColor: `hsl(${i * 45}, 70%, 50%)` } ]} > <Text style={styles.circularText}>{i + 1}</Text> </View> ); })} <View style={[styles.circularCenter, { left: centerX - 30, top: centerY - 30 }]}> <Text>Center</Text> </View> </View> ); }; // Aspect ratio boxes const renderAspectRatioBoxes = () => ( <View style={styles.aspectContainer}> <View style={styles.aspectRow}> <View style={[styles.aspectBox, { aspectRatio: 1, backgroundColor: '#FF3B30' }]}> <Text>1:1</Text> </View> <View style={[styles.aspectBox, { aspectRatio: 4/3, backgroundColor: '#FF9500' }]}> <Text>4:3</Text> </View> <View style={[styles.aspectBox, { aspectRatio: 16/9, backgroundColor: '#FFCC00' }]}> <Text>16:9</Text> </View> </View> <View style={styles.aspectRow}> <View style={[styles.aspectBox, { aspectRatio: 2/1, backgroundColor: '#4CD964' }]}> <Text>2:1</Text> </View> <View style={[styles.aspectBox, { aspectRatio: 3/2, backgroundColor: '#007AFF' }]}> <Text>3:2</Text> </View> <View style={[styles.aspectBox, { aspectRatio: 1/2, backgroundColor: '#5856D6' }]}> <Text>1:2</Text> </View> </View> </View> ); // Overlay patterns const renderOverlayPatterns = () => ( <View style={styles.overlayPatterns}> {/* Gradient overlay */} <View style={styles.overlayExample}> <View style={[styles.baseBox, { backgroundColor: '#007AFF' }]} /> <View style={[styles.overlay, styles.gradientOverlay]}> <Text style={styles.overlayWhiteText}>Gradient Overlay</Text> </View> </View> {/* Blur overlay (iOS only) */} {Platform.OS === 'ios' && ( <View style={styles.overlayExample}> <View style={[styles.baseBox, { backgroundColor: '#34C759' }]} /> <View style={[styles.overlay, styles.blurOverlay]}> <Text>Blur Effect</Text> </View> </View> )} {/* Semi-transparent overlay */} <View style={styles.overlayExample}> <View style={[styles.baseBox, { backgroundColor: '#FF3B30' }]} /> <View style={[styles.overlay, styles.semiTransparentOverlay]}> <Text style={styles.overlayWhiteText}>Semi-Transparent</Text> </View> </View> </View> ); // Responsive font scaling const renderResponsiveText = () => { const screenWidth = Dimensions.get('window').width; const baseFontSize = screenWidth < 375 ? 14 : screenWidth < 768 ? 16 : 18; return ( <View style={styles.responsiveTextContainer}> <Text style={[styles.responsiveTitle, { fontSize: baseFontSize + 6 }]}> Responsive Title </Text> <Text style={[styles.responsiveBody, { fontSize: baseFontSize }]}> This text scales based on screen width. On larger screens, it's bigger. On smaller screens like iPhone SE, it's more compact. </Text> <Text style={[styles.responsiveCaption, { fontSize: baseFontSize - 2, color: '#666' }]}> Caption text with adaptive sizing </Text> </View> ); }; return ( <ScrollView style={styles.container}> <Text style={styles.title}>Advanced Layout Techniques</Text> <Text style={styles.sectionTitle}>1. Masonry Grid (Pinterest-style)</Text> {renderMasonryGrid()} <Text style={styles.sectionTitle}>2. Circular Layout</Text> {renderCircularLayout()} <Text style={styles.sectionTitle}>3. Aspect Ratio Boxes</Text> {renderAspectRatioBoxes()} <Text style={styles.sectionTitle}>4. Overlay Patterns</Text> {renderOverlayPatterns()} <Text style={styles.sectionTitle}>5. Responsive Text Scaling</Text> {renderResponsiveText()} <View style={styles.tipsCard}> <Text style={styles.tipsTitle}>💡 Layout Tips</Text> <Text>• Use aspectRatio for consistent image sizing</Text> <Text>• Prefer flex over absolute positioning for responsive layouts</Text> <Text>• Use Dimensions.addEventListener for orientation changes</Text> <Text>• Test on multiple screen sizes (iPhone SE to iPad Pro)</Text> <Text>• Use Platform.select for platform-specific layouts</Text> </View> </ScrollView> ); }; const styles = StyleSheet.create({ container: { flex: 1, padding: 16, backgroundColor: '#fff' }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20 }, sectionTitle: { fontSize: 18, fontWeight: '600', marginTop: 20, marginBottom: 12 }, masonryContainer: { flexDirection: 'row', justifyContent: 'space-between' }, masonryColumn: { gap: 12 }, masonryItem: { borderRadius: 8, justifyContent: 'center', alignItems: 'center' }, masonryText: { color: '#fff', fontWeight: 'bold', fontSize: 18 }, circularContainer: { height: 300, position: 'relative', backgroundColor: '#f0f0f0', borderRadius: 12, marginBottom: 20 }, circularItem: { width: 40, height: 40, borderRadius: 20, justifyContent: 'center', alignItems: 'center' }, circularText: { color: '#fff', fontWeight: 'bold' }, circularCenter: { width: 60, height: 60, borderRadius: 30, backgroundColor: '#fff', position: 'absolute', justifyContent: 'center', alignItems: 'center', borderWidth: 2, borderColor: '#007AFF' }, aspectContainer: { gap: 12 }, aspectRow: { flexDirection: 'row', gap: 12 }, aspectBox: { flex: 1, justifyContent: 'center', alignItems: 'center', borderRadius: 8 }, overlayPatterns: { gap: 12 }, overlayExample: { position: 'relative', height: 100, borderRadius: 8, overflow: 'hidden' }, baseBox: { width: '100%', height: '100%' }, overlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center' }, gradientOverlay: { backgroundColor: 'rgba(0,0,0,0.4)' }, blurOverlay: { backgroundColor: 'rgba(255,255,255,0.7)' }, semiTransparentOverlay: { backgroundColor: 'rgba(0,0,0,0.5)' }, overlayWhiteText: { color: '#fff', fontWeight: 'bold' }, responsiveTextContainer: { backgroundColor: '#f0f0f0', padding: 16, borderRadius: 8, gap: 8 }, responsiveTitle: { fontWeight: 'bold' }, responsiveBody: { lineHeight: 24 }, responsiveCaption: {}, tipsCard: { marginTop: 20, marginBottom: 40, backgroundColor: '#e3f2fd', padding: 16, borderRadius: 12, gap: 8 }, tipsTitle: { fontSize: 16, fontWeight: 'bold', marginBottom: 8 } }); export default AdvancedLayouts;