Module 4: StyleSheet Deep Dive

Common Layout Patterns

Production-ready layouts you'll use in every app

Table of Contents

🎯 Learning Objectives

  • Build reusable screen scaffold layouts for consistent app structure
  • Create header variations for different navigation needs
  • Design card components that adapt to different content types
  • Implement list items for settings, contacts, and media content
  • Structure form layouts that work well on mobile
  • Build responsive grids for galleries and dashboards
  • Create overlay and modal patterns for dialogs and menus

Introduction

Now that you understand Flexbox fundamentals, it's time to see how these concepts combine into real-world layouts. Every mobile app is built from a relatively small set of layout patterns — headers, cards, lists, forms, grids, and modals. Master these patterns, and you can build virtually any screen.

This lesson is your pattern library. Each pattern is production-ready code you can adapt for your projects. We'll focus on the structural layout, keeping styling minimal so you can clearly see the flex mechanics at work.

📱 The 80/20 of Mobile Layouts

About 80% of mobile screens use combinations of just these patterns: a screen container with header/content/footer, horizontal rows for navigation, vertical stacks for content, cards for grouped information, and list items for repeating data. Learn these well, and layout becomes second nature.

How to Use This Lesson

Each pattern includes a visual representation, complete code, and notes on when to use it. Think of this as a reference you'll return to. When building a new screen, ask: "Which patterns do I need here?" Then combine them.

flowchart LR
    subgraph Patterns["Layout Pattern Library"]
        S["Screen Scaffolds"]
        H["Headers"]
        C["Cards"]
        L["List Items"]
        F["Forms"]
        G["Grids"]
        O["Overlays"]
    end
    
    Patterns --> Combine["Combine & Customize"]
    Combine --> Screen["Your App Screen"]
    
    style Patterns fill:#e3f2fd
    style Combine fill:#fff3e0
    style Screen fill:#e8f5e9
                

Screen Scaffolds

A screen scaffold is the outermost container that structures your entire screen. These patterns handle the relationship between headers, content areas, and footers.

Pattern: Basic Screen

The simplest scaffold — a full-screen container for your content.

Content flex: 1
import { View, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';

function BasicScreen({ children }) {
  return (
    <SafeAreaView style={styles.safeArea}>
      <View style={styles.screen}>
        {children}
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: '#fff',
  },
  screen: {
    flex: 1,
    padding: 16,
  },
});

Pattern: Screen with Fixed Header

Most screens need a header. This pattern keeps the header fixed while content scrolls.

Header Scrollable Content flex: 1
function ScreenWithHeader({ children, title }) {
  return (
    <SafeAreaView style={styles.safeArea}>
      {/* Fixed Header */}
      <View style={styles.header}>
        <Text style={styles.headerTitle}>{title}</Text>
      </View>
      
      {/* Scrollable Content */}
      <ScrollView 
        style={styles.content}
        contentContainerStyle={styles.contentContainer}
      >
        {children}
      </ScrollView>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: '#fff',
  },
  header: {
    height: 56,
    justifyContent: 'center',
    alignItems: 'center',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  headerTitle: {
    fontSize: 18,
    fontWeight: '600',
  },
  content: {
    flex: 1,
  },
  contentContainer: {
    padding: 16,
  },
});

Pattern: Screen with Header and Footer

Common for checkout flows, forms with submit buttons, or any screen needing persistent bottom actions.

Header Content flex: 1 Submit
function ScreenWithHeaderFooter({ children, title, onSubmit }) {
  return (
    <SafeAreaView style={styles.safeArea}>
      {/* Header */}
      <View style={styles.header}>
        <Text style={styles.headerTitle}>{title}</Text>
      </View>
      
      {/* Scrollable Content */}
      <ScrollView style={styles.content}>
        {children}
      </ScrollView>
      
      {/* Fixed Footer */}
      <View style={styles.footer}>
        <Pressable style={styles.submitButton} onPress={onSubmit}>
          <Text style={styles.submitText}>Submit</Text>
        </Pressable>
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: '#fff',
  },
  header: {
    height: 56,
    justifyContent: 'center',
    alignItems: 'center',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  headerTitle: {
    fontSize: 18,
    fontWeight: '600',
  },
  content: {
    flex: 1,  // Takes all space between header and footer
  },
  footer: {
    padding: 16,
    borderTopWidth: 1,
    borderTopColor: '#e0e0e0',
  },
  submitButton: {
    backgroundColor: '#4CAF50',
    paddingVertical: 14,
    borderRadius: 8,
    alignItems: 'center',
  },
  submitText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
});

Pattern: Tab Screen with Bottom Navigation

The standard pattern for apps with bottom tab navigation.

Screen Content 🏠 🔍 👤
function TabScreen({ children, activeTab, onTabPress }) {
  return (
    <SafeAreaView style={styles.safeArea}>
      {/* Main Content */}
      <View style={styles.content}>
        {children}
      </View>
      
      {/* Bottom Tab Bar */}
      <View style={styles.tabBar}>
        {['home', 'search', 'profile'].map((tab) => (
          <Pressable 
            key={tab}
            style={styles.tab}
            onPress={() => onTabPress(tab)}
          >
            <Text style={styles.tabIcon}>
              {tab === 'home' ? '🏠' : tab === 'search' ? '🔍' : '👤'}
            </Text>
            {activeTab === tab && <View style={styles.activeIndicator} />}
          </Pressable>
        ))}
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: '#fff',
  },
  content: {
    flex: 1,
  },
  tabBar: {
    flexDirection: 'row',
    height: 56,
    borderTopWidth: 1,
    borderTopColor: '#e0e0e0',
  },
  tab: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  tabIcon: {
    fontSize: 24,
  },
  activeIndicator: {
    position: 'absolute',
    bottom: 8,
    width: 24,
    height: 3,
    backgroundColor: '#2196F3',
    borderRadius: 2,
  },
});

💡 When to Use Each Scaffold

  • Basic Screen: Simple content pages, detail views
  • Header + Content: Most screens in your app
  • Header + Content + Footer: Forms, checkout, multi-step flows
  • Tab Screen: Main navigation shell (usually provided by navigation library)

Header Patterns

Headers need to accommodate various combinations of navigation, titles, and actions. Here are the patterns that cover most needs.

Pattern: Simple Centered Title

function SimpleHeader({ title }) {
  return (
    <View style={styles.header}>
      <Text style={styles.title}>{title}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  header: {
    height: 56,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#fff',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  title: {
    fontSize: 18,
    fontWeight: '600',
    color: '#1a1a1a',
  },
});

Pattern: Back Button + Title

Page Title
function HeaderWithBack({ title, onBack }) {
  return (
    <View style={styles.header}>
      <Pressable style={styles.backButton} onPress={onBack}>
        <Text style={styles.backIcon}>←</Text>
      </Pressable>
      
      <Text style={styles.title}>{title}</Text>
      
      {/* Empty view for balance */}
      <View style={styles.placeholder} />
    </View>
  );
}

const styles = StyleSheet.create({
  header: {
    height: 56,
    flexDirection: 'row',
    alignItems: 'center',
    paddingHorizontal: 8,
    backgroundColor: '#fff',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  backButton: {
    width: 44,
    height: 44,
    justifyContent: 'center',
    alignItems: 'center',
  },
  backIcon: {
    fontSize: 24,
    color: '#2196F3',
  },
  title: {
    flex: 1,
    fontSize: 18,
    fontWeight: '600',
    textAlign: 'center',
    color: '#1a1a1a',
  },
  placeholder: {
    width: 44,  // Same as back button for centering
  },
});

Pattern: Back + Title + Actions

Edit Profile Save
function HeaderWithActions({ title, onBack, onSave }) {
  return (
    <View style={styles.header}>
      <Pressable style={styles.button} onPress={onBack}>
        <Text style={styles.backIcon}>←</Text>
      </Pressable>
      
      <Text style={styles.title}>{title}</Text>
      
      <Pressable style={styles.button} onPress={onSave}>
        <Text style={styles.actionText}>Save</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  header: {
    height: 56,
    flexDirection: 'row',
    alignItems: 'center',
    paddingHorizontal: 8,
    backgroundColor: '#fff',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  button: {
    minWidth: 44,
    height: 44,
    justifyContent: 'center',
    alignItems: 'center',
    paddingHorizontal: 8,
  },
  backIcon: {
    fontSize: 24,
    color: '#2196F3',
  },
  title: {
    flex: 1,
    fontSize: 18,
    fontWeight: '600',
    textAlign: 'center',
    color: '#1a1a1a',
  },
  actionText: {
    fontSize: 16,
    fontWeight: '600',
    color: '#2196F3',
  },
});

Pattern: Search Header

🔍 Search... Cancel
function SearchHeader({ value, onChangeText, onCancel }) {
  return (
    <View style={styles.header}>
      <View style={styles.searchContainer}>
        <Text style={styles.searchIcon}>🔍</Text>
        <TextInput
          style={styles.searchInput}
          placeholder="Search..."
          value={value}
          onChangeText={onChangeText}
          autoFocus
        />
      </View>
      
      <Pressable style={styles.cancelButton} onPress={onCancel}>
        <Text style={styles.cancelText}>Cancel</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  header: {
    height: 56,
    flexDirection: 'row',
    alignItems: 'center',
    paddingHorizontal: 12,
    backgroundColor: '#fff',
    gap: 12,
  },
  searchContainer: {
    flex: 1,
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f5f5f5',
    borderRadius: 20,
    paddingHorizontal: 12,
    height: 40,
  },
  searchIcon: {
    fontSize: 16,
    marginRight: 8,
  },
  searchInput: {
    flex: 1,
    fontSize: 16,
    color: '#1a1a1a',
  },
  cancelButton: {
    paddingVertical: 8,
  },
  cancelText: {
    fontSize: 16,
    color: '#2196F3',
  },
});

Pattern: Logo + Actions Header

A AppName 🔔 ⚙️
function LogoHeader({ onNotifications, onSettings }) {
  return (
    <View style={styles.header}>
      {/* Logo area */}
      <View style={styles.logoContainer}>
        <View style={styles.logoIcon}>
          <Text style={styles.logoLetter}>A</Text>
        </View>
        <Text style={styles.logoText}>AppName</Text>
      </View>
      
      {/* Spacer */}
      <View style={styles.spacer} />
      
      {/* Action buttons */}
      <View style={styles.actions}>
        <Pressable style={styles.iconButton} onPress={onNotifications}>
          <Text style={styles.icon}>🔔</Text>
        </Pressable>
        <Pressable style={styles.iconButton} onPress={onSettings}>
          <Text style={styles.icon}>⚙️</Text>
        </Pressable>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  header: {
    height: 56,
    flexDirection: 'row',
    alignItems: 'center',
    paddingHorizontal: 12,
    backgroundColor: '#fff',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  logoContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 8,
  },
  logoIcon: {
    width: 32,
    height: 32,
    backgroundColor: '#2196F3',
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
  },
  logoLetter: {
    color: '#fff',
    fontSize: 16,
    fontWeight: 'bold',
  },
  logoText: {
    fontSize: 18,
    fontWeight: '700',
    color: '#1a1a1a',
  },
  spacer: {
    flex: 1,
  },
  actions: {
    flexDirection: 'row',
    gap: 4,
  },
  iconButton: {
    width: 40,
    height: 40,
    justifyContent: 'center',
    alignItems: 'center',
  },
  icon: {
    fontSize: 22,
  },
});

Card Patterns

Cards are self-contained units of content. They group related information and provide a visual boundary. Here are the most common card layouts.

Pattern: Basic Content Card

Card Title This is a description that provides additional context about the card. Learn More →
function ContentCard({ title, description, onPress }) {
  return (
    <Pressable style={styles.card} onPress={onPress}>
      <Text style={styles.title}>{title}</Text>
      <Text style={styles.description}>{description}</Text>
      <Text style={styles.link}>Learn More →</Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 16,
    // iOS Shadow
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 8,
    // Android Shadow
    elevation: 3,
  },
  title: {
    fontSize: 18,
    fontWeight: '600',
    color: '#1a1a1a',
    marginBottom: 8,
  },
  description: {
    fontSize: 14,
    color: '#666',
    lineHeight: 20,
    marginBottom: 12,
  },
  link: {
    fontSize: 14,
    fontWeight: '600',
    color: '#2196F3',
  },
});

Pattern: Horizontal Card (Image + Content)

📷 Product Name Short description here $29.99
function HorizontalCard({ image, title, subtitle, price }) {
  return (
    <View style={styles.card}>
      <Image source={{ uri: image }} style={styles.image} />
      <View style={styles.content}>
        <Text style={styles.title}>{title}</Text>
        <Text style={styles.subtitle}>{subtitle}</Text>
        <Text style={styles.price}>{price}</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    flexDirection: 'row',
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 12,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 2,
  },
  image: {
    width: 80,
    height: 80,
    borderRadius: 8,
    backgroundColor: '#f0f0f0',
  },
  content: {
    flex: 1,
    marginLeft: 12,
    justifyContent: 'center',
  },
  title: {
    fontSize: 16,
    fontWeight: '600',
    color: '#1a1a1a',
  },
  subtitle: {
    fontSize: 14,
    color: '#666',
    marginTop: 4,
  },
  price: {
    fontSize: 16,
    fontWeight: '700',
    color: '#4CAF50',
    marginTop: 4,
  },
});

Pattern: Vertical Card (Image Top)

🏔️ Mountain View Beautiful scenery 📍 Colorado, USA ★★★★☆ (4.2)
function VerticalCard({ image, title, subtitle, location, rating }) {
  return (
    <View style={styles.card}>
      <Image source={{ uri: image }} style={styles.image} />
      <View style={styles.content}>
        <Text style={styles.title}>{title}</Text>
        <Text style={styles.subtitle}>{subtitle}</Text>
        <Text style={styles.location}>📍 {location}</Text>
        <View style={styles.ratingRow}>
          <Text style={styles.stars}>{'★'.repeat(Math.floor(rating))}{'☆'.repeat(5 - Math.floor(rating))}</Text>
          <Text style={styles.ratingText}>({rating})</Text>
        </View>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#fff',
    borderRadius: 12,
    overflow: 'hidden',  // Clips image to border radius
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 2,
  },
  image: {
    width: '100%',
    height: 140,
    backgroundColor: '#f0f0f0',
  },
  content: {
    padding: 12,
  },
  title: {
    fontSize: 16,
    fontWeight: '600',
    color: '#1a1a1a',
  },
  subtitle: {
    fontSize: 14,
    color: '#666',
    marginTop: 4,
  },
  location: {
    fontSize: 12,
    color: '#999',
    marginTop: 8,
  },
  ratingRow: {
    flexDirection: 'row',
    alignItems: 'center',
    marginTop: 8,
    gap: 4,
  },
  stars: {
    color: '#ffc107',
    fontSize: 14,
  },
  ratingText: {
    fontSize: 12,
    color: '#666',
  },
});

Pattern: Stats Card

1,234 Followers 567 Following 89 Posts
function StatsCard({ stats }) {
  return (
    <View style={styles.card}>
      {stats.map((stat, index) => (
        <React.Fragment key={stat.label}>
          {index > 0 && <View style={styles.divider} />}
          <View style={styles.stat}>
            <Text style={styles.statValue}>{stat.value}</Text>
            <Text style={styles.statLabel}>{stat.label}</Text>
          </View>
        </React.Fragment>
      ))}
    </View>
  );
}

// Usage:
// <StatsCard stats={[
//   { value: '1,234', label: 'Followers' },
//   { value: '567', label: 'Following' },
//   { value: '89', label: 'Posts' },
// ]} />

const styles = StyleSheet.create({
  card: {
    flexDirection: 'row',
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 16,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 2,
  },
  stat: {
    flex: 1,
    alignItems: 'center',
  },
  statValue: {
    fontSize: 24,
    fontWeight: '700',
    color: '#1a1a1a',
  },
  statLabel: {
    fontSize: 13,
    color: '#666',
    marginTop: 4,
  },
  divider: {
    width: 1,
    backgroundColor: '#e0e0e0',
    marginHorizontal: 8,
  },
});

List Item Patterns

List items are the building blocks of scrollable content. Whether you're building a settings screen, contact list, or feed, these patterns have you covered.

Pattern: Simple List Item

List Item Title
function SimpleListItem({ title, onPress }) {
  return (
    <Pressable style={styles.item} onPress={onPress}>
      <Text style={styles.title}>{title}</Text>
      <Text style={styles.chevron}>›</Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  item: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    paddingVertical: 16,
    paddingHorizontal: 16,
    backgroundColor: '#fff',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  title: {
    fontSize: 16,
    color: '#1a1a1a',
  },
  chevron: {
    fontSize: 20,
    color: '#999',
  },
});

Pattern: List Item with Icon

⚙️ Settings
function IconListItem({ icon, title, onPress }) {
  return (
    <Pressable style={styles.item} onPress={onPress}>
      <View style={styles.iconContainer}>
        <Text style={styles.icon}>{icon}</Text>
      </View>
      <Text style={styles.title}>{title}</Text>
      <Text style={styles.chevron}>›</Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  item: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingVertical: 12,
    paddingHorizontal: 16,
    backgroundColor: '#fff',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  iconContainer: {
    width: 36,
    height: 36,
    backgroundColor: '#e3f2fd',
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
    marginRight: 12,
  },
  icon: {
    fontSize: 18,
  },
  title: {
    flex: 1,
    fontSize: 16,
    color: '#1a1a1a',
  },
  chevron: {
    fontSize: 20,
    color: '#999',
  },
});

Pattern: Contact/User List Item

JD John Doe Software Engineer
function ContactListItem({ name, subtitle, avatarColor, isOnline, onPress }) {
  const initials = name.split(' ').map(n => n[0]).join('').slice(0, 2);
  
  return (
    <Pressable style={styles.item} onPress={onPress}>
      <View style={[styles.avatar, { backgroundColor: avatarColor }]}>
        <Text style={styles.initials}>{initials}</Text>
      </View>
      
      <View style={styles.textContainer}>
        <Text style={styles.name}>{name}</Text>
        <Text style={styles.subtitle}>{subtitle}</Text>
      </View>
      
      {isOnline && <View style={styles.onlineIndicator} />}
    </Pressable>
  );
}

const styles = StyleSheet.create({
  item: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingVertical: 12,
    paddingHorizontal: 16,
    backgroundColor: '#fff',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  avatar: {
    width: 48,
    height: 48,
    borderRadius: 24,
    justifyContent: 'center',
    alignItems: 'center',
    marginRight: 12,
  },
  initials: {
    color: '#fff',
    fontSize: 18,
    fontWeight: '600',
  },
  textContainer: {
    flex: 1,
  },
  name: {
    fontSize: 16,
    fontWeight: '600',
    color: '#1a1a1a',
  },
  subtitle: {
    fontSize: 14,
    color: '#666',
    marginTop: 2,
  },
  onlineIndicator: {
    width: 10,
    height: 10,
    borderRadius: 5,
    backgroundColor: '#4CAF50',
  },
});

Pattern: Settings Item with Toggle

🔔 Push Notifications Receive alerts for new messages
import { Switch } from 'react-native';

function SettingsToggleItem({ icon, title, subtitle, value, onValueChange }) {
  return (
    <View style={styles.item}>
      <Text style={styles.icon}>{icon}</Text>
      
      <View style={styles.textContainer}>
        <Text style={styles.title}>{title}</Text>
        {subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
      </View>
      
      <Switch
        value={value}
        onValueChange={onValueChange}
        trackColor={{ false: '#e0e0e0', true: '#81c784' }}
        thumbColor={value ? '#4CAF50' : '#f5f5f5'}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  item: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingVertical: 12,
    paddingHorizontal: 16,
    backgroundColor: '#fff',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  icon: {
    fontSize: 20,
    marginRight: 12,
  },
  textContainer: {
    flex: 1,
  },
  title: {
    fontSize: 16,
    color: '#1a1a1a',
  },
  subtitle: {
    fontSize: 13,
    color: '#666',
    marginTop: 2,
  },
});

Form Layouts

Forms require careful layout to be usable on mobile. These patterns ensure good touch targets and clear visual hierarchy.

Pattern: Labeled Input

function LabeledInput({ label, placeholder, value, onChangeText, error }) {
  return (
    <View style={styles.inputGroup}>
      <Text style={styles.label}>{label}</Text>
      <TextInput
        style={[styles.input, error && styles.inputError]}
        placeholder={placeholder}
        value={value}
        onChangeText={onChangeText}
        placeholderTextColor="#999"
      />
      {error && <Text style={styles.errorText}>{error}</Text>}
    </View>
  );
}

const styles = StyleSheet.create({
  inputGroup: {
    marginBottom: 20,
  },
  label: {
    fontSize: 14,
    fontWeight: '600',
    color: '#1a1a1a',
    marginBottom: 8,
  },
  input: {
    height: 48,
    borderWidth: 1,
    borderColor: '#e0e0e0',
    borderRadius: 8,
    paddingHorizontal: 16,
    fontSize: 16,
    color: '#1a1a1a',
    backgroundColor: '#fff',
  },
  inputError: {
    borderColor: '#f44336',
  },
  errorText: {
    fontSize: 12,
    color: '#f44336',
    marginTop: 4,
  },
});

Pattern: Form Section with Header

function FormSection({ title, children }) {
  return (
    <View style={styles.section}>
      <Text style={styles.sectionTitle}>{title}</Text>
      <View style={styles.sectionContent}>
        {children}
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  section: {
    marginBottom: 24,
  },
  sectionTitle: {
    fontSize: 13,
    fontWeight: '600',
    color: '#666',
    textTransform: 'uppercase',
    letterSpacing: 0.5,
    marginBottom: 12,
    paddingHorizontal: 16,
  },
  sectionContent: {
    backgroundColor: '#fff',
    borderRadius: 12,
    overflow: 'hidden',
  },
});

Pattern: Complete Login Form

function LoginForm({ onLogin }) {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  
  return (
    <View style={styles.form}>
      {/* Email Input */}
      <View style={styles.inputGroup}>
        <Text style={styles.label}>Email</Text>
        <TextInput
          style={styles.input}
          placeholder="your@email.com"
          value={email}
          onChangeText={setEmail}
          keyboardType="email-address"
          autoCapitalize="none"
          autoComplete="email"
        />
      </View>
      
      {/* Password Input */}
      <View style={styles.inputGroup}>
        <Text style={styles.label}>Password</Text>
        <TextInput
          style={styles.input}
          placeholder="Enter password"
          value={password}
          onChangeText={setPassword}
          secureTextEntry
          autoComplete="password"
        />
      </View>
      
      {/* Forgot Password */}
      <Pressable style={styles.forgotButton}>
        <Text style={styles.forgotText}>Forgot Password?</Text>
      </Pressable>
      
      {/* Submit Button */}
      <Pressable style={styles.submitButton} onPress={onLogin}>
        <Text style={styles.submitText}>Log In</Text>
      </Pressable>
      
      {/* Divider */}
      <View style={styles.divider}>
        <View style={styles.dividerLine} />
        <Text style={styles.dividerText}>or</Text>
        <View style={styles.dividerLine} />
      </View>
      
      {/* Social Login */}
      <View style={styles.socialButtons}>
        <Pressable style={styles.socialButton}>
          <Text>🍎 Apple</Text>
        </Pressable>
        <Pressable style={styles.socialButton}>
          <Text>🔵 Google</Text>
        </Pressable>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  form: {
    padding: 24,
  },
  inputGroup: {
    marginBottom: 20,
  },
  label: {
    fontSize: 14,
    fontWeight: '600',
    color: '#1a1a1a',
    marginBottom: 8,
  },
  input: {
    height: 50,
    borderWidth: 1,
    borderColor: '#e0e0e0',
    borderRadius: 10,
    paddingHorizontal: 16,
    fontSize: 16,
    backgroundColor: '#fff',
  },
  forgotButton: {
    alignSelf: 'flex-end',
    marginBottom: 24,
  },
  forgotText: {
    color: '#2196F3',
    fontSize: 14,
  },
  submitButton: {
    height: 50,
    backgroundColor: '#2196F3',
    borderRadius: 10,
    justifyContent: 'center',
    alignItems: 'center',
  },
  submitText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
  divider: {
    flexDirection: 'row',
    alignItems: 'center',
    marginVertical: 24,
  },
  dividerLine: {
    flex: 1,
    height: 1,
    backgroundColor: '#e0e0e0',
  },
  dividerText: {
    marginHorizontal: 16,
    color: '#666',
    fontSize: 14,
  },
  socialButtons: {
    flexDirection: 'row',
    gap: 12,
  },
  socialButton: {
    flex: 1,
    height: 50,
    borderWidth: 1,
    borderColor: '#e0e0e0',
    borderRadius: 10,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

Grid Layouts

While React Native doesn't have CSS Grid, you can create grid layouts using Flexbox with flexWrap. These patterns are essential for galleries, dashboards, and icon menus.

Pattern: 2-Column Grid

Item 1 Item 2 Item 3 Item 4
function TwoColumnGrid({ items, renderItem }) {
  return (
    <View style={styles.grid}>
      {items.map((item, index) => (
        <View key={index} style={styles.gridItem}>
          {renderItem(item, index)}
        </View>
      ))}
    </View>
  );
}

const styles = StyleSheet.create({
  grid: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    gap: 12,
    padding: 12,
  },
  gridItem: {
    width: '48%',  // Slightly less than 50% to account for gap
    // Or use exact calculation:
    // width: (screenWidth - 36) / 2, // 12px padding * 2 + 12px gap
  },
});

Pattern: 3-Column Icon Grid

📷 Camera 🎵 Music 📁 Files
function IconGrid({ items }) {
  return (
    <View style={styles.grid}>
      {items.map((item) => (
        <Pressable key={item.id} style={styles.iconItem} onPress={item.onPress}>
          <View style={styles.iconContainer}>
            <Text style={styles.icon}>{item.icon}</Text>
          </View>
          <Text style={styles.iconLabel}>{item.label}</Text>
        </Pressable>
      ))}
    </View>
  );
}

// Usage:
// <IconGrid items={[
//   { id: '1', icon: '📷', label: 'Camera', onPress: () => {} },
//   { id: '2', icon: '🎵', label: 'Music', onPress: () => {} },
//   { id: '3', icon: '📁', label: 'Files', onPress: () => {} },
// ]} />

const styles = StyleSheet.create({
  grid: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    justifyContent: 'space-around',
    padding: 16,
  },
  iconItem: {
    width: '30%',
    alignItems: 'center',
    marginBottom: 20,
  },
  iconContainer: {
    width: 64,
    height: 64,
    backgroundColor: '#f5f5f5',
    borderRadius: 16,
    justifyContent: 'center',
    alignItems: 'center',
    marginBottom: 8,
  },
  icon: {
    fontSize: 28,
  },
  iconLabel: {
    fontSize: 12,
    color: '#666',
    textAlign: 'center',
  },
});

Pattern: Photo Gallery Grid

function PhotoGallery({ photos, numColumns = 3 }) {
  const { width } = useWindowDimensions();
  const gap = 4;
  const itemSize = (width - gap * (numColumns + 1)) / numColumns;
  
  return (
    <View style={styles.gallery}>
      {photos.map((photo, index) => (
        <Pressable 
          key={photo.id} 
          style={[styles.photoItem, { width: itemSize, height: itemSize }]}
        >
          <Image
            source={{ uri: photo.url }}
            style={styles.photo}
            resizeMode="cover"
          />
        </Pressable>
      ))}
    </View>
  );
}

const styles = StyleSheet.create({
  gallery: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    gap: 4,
    padding: 4,
  },
  photoItem: {
    // width and height set dynamically
  },
  photo: {
    width: '100%',
    height: '100%',
  },
});

Pattern: Dashboard Stats Grid

function DashboardGrid({ stats }) {
  return (
    <View style={styles.dashboard}>
      {stats.map((stat) => (
        <View key={stat.id} style={styles.statCard}>
          <Text style={styles.statIcon}>{stat.icon}</Text>
          <Text style={styles.statValue}>{stat.value}</Text>
          <Text style={styles.statLabel}>{stat.label}</Text>
        </View>
      ))}
    </View>
  );
}

const styles = StyleSheet.create({
  dashboard: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    gap: 12,
    padding: 16,
  },
  statCard: {
    width: '47%',
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 16,
    alignItems: 'center',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 2,
  },
  statIcon: {
    fontSize: 32,
    marginBottom: 8,
  },
  statValue: {
    fontSize: 28,
    fontWeight: '700',
    color: '#1a1a1a',
  },
  statLabel: {
    fontSize: 13,
    color: '#666',
    marginTop: 4,
  },
});

Overlay & Modal Patterns

Overlays and modals require careful positioning. These patterns create professional dialogs, bottom sheets, and action menus.

Pattern: Centered Modal

Confirm Action Are you sure you want to proceed with this action? Cancel Confirm
function CenteredModal({ visible, title, message, onCancel, onConfirm }) {
  if (!visible) return null;
  
  return (
    <View style={styles.overlay}>
      <View style={styles.modal}>
        <Text style={styles.title}>{title}</Text>
        <Text style={styles.message}>{message}</Text>
        
        <View style={styles.buttonRow}>
          <Pressable style={styles.cancelButton} onPress={onCancel}>
            <Text style={styles.cancelText}>Cancel</Text>
          </Pressable>
          <Pressable style={styles.confirmButton} onPress={onConfirm}>
            <Text style={styles.confirmText}>Confirm</Text>
          </Pressable>
        </View>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  overlay: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    justifyContent: 'center',
    alignItems: 'center',
    padding: 24,
  },
  modal: {
    width: '100%',
    maxWidth: 320,
    backgroundColor: '#fff',
    borderRadius: 16,
    padding: 24,
  },
  title: {
    fontSize: 18,
    fontWeight: '600',
    color: '#1a1a1a',
    textAlign: 'center',
    marginBottom: 12,
  },
  message: {
    fontSize: 15,
    color: '#666',
    textAlign: 'center',
    lineHeight: 22,
    marginBottom: 24,
  },
  buttonRow: {
    flexDirection: 'row',
    gap: 12,
  },
  cancelButton: {
    flex: 1,
    height: 44,
    backgroundColor: '#f5f5f5',
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
  },
  cancelText: {
    fontSize: 15,
    fontWeight: '600',
    color: '#666',
  },
  confirmButton: {
    flex: 1,
    height: 44,
    backgroundColor: '#2196F3',
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
  },
  confirmText: {
    fontSize: 15,
    fontWeight: '600',
    color: '#fff',
  },
});

Pattern: Bottom Sheet

📷 Take Photo 🖼️ Choose from Library 📁 Browse Files ✕ Cancel
function BottomSheet({ visible, options, onSelect, onClose }) {
  if (!visible) return null;
  
  return (
    <Pressable style={styles.overlay} onPress={onClose}>
      <Pressable style={styles.sheet}>
        {/* Handle */}
        <View style={styles.handle} />
        
        {/* Options */}
        {options.map((option) => (
          <Pressable
            key={option.id}
            style={styles.option}
            onPress={() => onSelect(option)}
          >
            <Text style={styles.optionIcon}>{option.icon}</Text>
            <Text style={[
              styles.optionText,
              option.destructive && styles.destructiveText,
            ]}>
              {option.label}
            </Text>
          </Pressable>
        ))}
      </Pressable>
    </Pressable>
  );
}

// Usage:
// <BottomSheet
//   visible={showSheet}
//   options={[
//     { id: '1', icon: '📷', label: 'Take Photo' },
//     { id: '2', icon: '🖼️', label: 'Choose from Library' },
//     { id: '3', icon: '✕', label: 'Cancel', destructive: true },
//   ]}
//   onSelect={(opt) => handleOption(opt)}
//   onClose={() => setShowSheet(false)}
// />

const styles = StyleSheet.create({
  overlay: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    justifyContent: 'flex-end',
  },
  sheet: {
    backgroundColor: '#fff',
    borderTopLeftRadius: 16,
    borderTopRightRadius: 16,
    paddingBottom: 34, // Safe area
  },
  handle: {
    width: 40,
    height: 4,
    backgroundColor: '#e0e0e0',
    borderRadius: 2,
    alignSelf: 'center',
    marginTop: 12,
    marginBottom: 20,
  },
  option: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingVertical: 16,
    paddingHorizontal: 24,
  },
  optionIcon: {
    fontSize: 20,
    marginRight: 16,
  },
  optionText: {
    fontSize: 16,
    color: '#1a1a1a',
  },
  destructiveText: {
    color: '#f44336',
  },
});

Pattern: Toast Notification

function Toast({ visible, message, type = 'info' }) {
  if (!visible) return null;
  
  const backgroundColor = {
    info: '#323232',
    success: '#4CAF50',
    error: '#f44336',
  }[type];
  
  return (
    <View style={[styles.toast, { backgroundColor }]}>
      <Text style={styles.toastText}>{message}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  toast: {
    position: 'absolute',
    bottom: 100,
    left: 24,
    right: 24,
    paddingVertical: 14,
    paddingHorizontal: 20,
    borderRadius: 8,
    alignItems: 'center',
  },
  toastText: {
    color: '#fff',
    fontSize: 15,
    fontWeight: '500',
  },
});

Empty States & Loading

Empty states and loading indicators need clear visual communication. These patterns ensure users always know what's happening.

Pattern: Empty State

📭 No Messages Yet Start a conversation to see your messages here New Message
function EmptyState({ icon, title, message, actionLabel, onAction }) {
  return (
    <View style={styles.container}>
      <Text style={styles.icon}>{icon}</Text>
      <Text style={styles.title}>{title}</Text>
      <Text style={styles.message}>{message}</Text>
      
      {actionLabel && (
        <Pressable style={styles.actionButton} onPress={onAction}>
          <Text style={styles.actionText}>{actionLabel}</Text>
        </Pressable>
      )}
    </View>
  );
}

// Usage:
// <EmptyState
//   icon="📭"
//   title="No Messages Yet"
//   message="Start a conversation to see your messages here"
//   actionLabel="New Message"
//   onAction={() => navigation.navigate('Compose')}
// />

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 32,
  },
  icon: {
    fontSize: 64,
    marginBottom: 16,
  },
  title: {
    fontSize: 20,
    fontWeight: '600',
    color: '#1a1a1a',
    textAlign: 'center',
    marginBottom: 8,
  },
  message: {
    fontSize: 15,
    color: '#666',
    textAlign: 'center',
    lineHeight: 22,
    marginBottom: 24,
  },
  actionButton: {
    backgroundColor: '#2196F3',
    paddingVertical: 12,
    paddingHorizontal: 32,
    borderRadius: 24,
  },
  actionText: {
    color: '#fff',
    fontSize: 15,
    fontWeight: '600',
  },
});

Pattern: Loading Screen

function LoadingScreen({ message = 'Loading...' }) {
  return (
    <View style={styles.container}>
      <ActivityIndicator size="large" color="#2196F3" />
      <Text style={styles.message}>{message}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#fff',
  },
  message: {
    marginTop: 16,
    fontSize: 16,
    color: '#666',
  },
});

Pattern: Skeleton Loading

function SkeletonCard() {
  return (
    <View style={styles.card}>
      <View style={styles.skeletonImage} />
      <View style={styles.skeletonContent}>
        <View style={[styles.skeletonLine, { width: '70%' }]} />
        <View style={[styles.skeletonLine, { width: '90%' }]} />
        <View style={[styles.skeletonLine, { width: '50%' }]} />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    flexDirection: 'row',
    padding: 16,
    backgroundColor: '#fff',
  },
  skeletonImage: {
    width: 80,
    height: 80,
    backgroundColor: '#e0e0e0',
    borderRadius: 8,
  },
  skeletonContent: {
    flex: 1,
    marginLeft: 12,
    justifyContent: 'center',
    gap: 8,
  },
  skeletonLine: {
    height: 14,
    backgroundColor: '#e0e0e0',
    borderRadius: 4,
  },
});

Composing Patterns Together

Real screens combine multiple patterns. Here's how to think about composition:

flowchart TB
    subgraph Screen["Complete Screen"]
        direction TB
        H["Header Pattern"]
        C["Content Area"]
        F["Footer Pattern"]
    end
    
    subgraph ContentArea["Content Breakdown"]
        Card1["Card Pattern"]
        List["List Items"]
        Grid["Grid Pattern"]
    end
    
    C --> ContentArea
    
    style Screen fill:#e3f2fd
    style ContentArea fill:#e8f5e9
                

Example: Complete Profile Screen

function ProfileScreen() {
  return (
    <SafeAreaView style={styles.safeArea}>
      {/* Header */}
      <HeaderWithActions 
        title="Profile" 
        onBack={() => navigation.goBack()}
        onSave={() => handleSave()}
      />
      
      <ScrollView style={styles.content}>
        {/* Profile Card */}
        <View style={styles.profileSection}>
          <ContactListItem
            name="Jane Developer"
            subtitle="React Native Engineer"
            avatarColor="#9c27b0"
            isOnline={true}
          />
        </View>
        
        {/* Stats */}
        <StatsCard stats={[
          { value: '142', label: 'Projects' },
          { value: '8.5k', label: 'Followers' },
          { value: '284', label: 'Following' },
        ]} />
        
        {/* Settings Section */}
        <FormSection title="Settings">
          <SettingsToggleItem
            icon="🔔"
            title="Notifications"
            subtitle="Receive push notifications"
            value={notifications}
            onValueChange={setNotifications}
          />
          <IconListItem icon="🔒" title="Privacy" onPress={() => {}} />
          <IconListItem icon="❓" title="Help" onPress={() => {}} />
        </FormSection>
      </ScrollView>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  content: {
    flex: 1,
  },
  profileSection: {
    backgroundColor: '#fff',
    marginBottom: 16,
  },
});

✅ Composition Best Practices

  • Start with scaffold: Choose your screen structure first
  • Identify regions: Break the screen into header, content sections, footer
  • Match patterns to data: Use cards for grouped info, lists for repeating items
  • Keep patterns small: Each pattern should do one thing well
  • Customize via props: Make patterns flexible with good prop interfaces

Hands-On Exercises

Exercise 1: Build a Settings Screen

Create a settings screen with a header, multiple sections (Account, Notifications, Privacy), and various list item types including toggles.

Show Solution Approach

Combine these patterns:

  1. ScreenWithHeader scaffold
  2. FormSection for each settings group
  3. IconListItem for navigation items
  4. SettingsToggleItem for toggles
function SettingsScreen() {
  const [darkMode, setDarkMode] = useState(false);
  const [notifications, setNotifications] = useState(true);
  
  return (
    <ScreenWithHeader title="Settings">
      <FormSection title="Account">
        <IconListItem icon="👤" title="Edit Profile" onPress={() => {}} />
        <IconListItem icon="🔑" title="Change Password" onPress={() => {}} />
      </FormSection>
      
      <FormSection title="Preferences">
        <SettingsToggleItem
          icon="🌙"
          title="Dark Mode"
          value={darkMode}
          onValueChange={setDarkMode}
        />
        <SettingsToggleItem
          icon="🔔"
          title="Notifications"
          value={notifications}
          onValueChange={setNotifications}
        />
      </FormSection>
      
      <FormSection title="Support">
        <IconListItem icon="❓" title="Help Center" onPress={() => {}} />
        <IconListItem icon="📧" title="Contact Us" onPress={() => {}} />
      </FormSection>
    </ScreenWithHeader>
  );
}

Exercise 2: Build a Product Grid

Create a 2-column product grid where each product shows an image, name, and price using the vertical card pattern.

Show Solution
function ProductGrid({ products }) {
  return (
    <View style={styles.grid}>
      {products.map((product) => (
        <Pressable key={product.id} style={styles.productCard}>
          <Image source={{ uri: product.image }} style={styles.productImage} />
          <View style={styles.productInfo}>
            <Text style={styles.productName} numberOfLines={2}>
              {product.name}
            </Text>
            <Text style={styles.productPrice}>${product.price}</Text>
          </View>
        </Pressable>
      ))}
    </View>
  );
}

const styles = StyleSheet.create({
  grid: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    gap: 12,
    padding: 12,
  },
  productCard: {
    width: '47%',
    backgroundColor: '#fff',
    borderRadius: 12,
    overflow: 'hidden',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 2,
  },
  productImage: {
    width: '100%',
    aspectRatio: 1,
    backgroundColor: '#f5f5f5',
  },
  productInfo: {
    padding: 12,
  },
  productName: {
    fontSize: 14,
    color: '#1a1a1a',
    marginBottom: 4,
  },
  productPrice: {
    fontSize: 16,
    fontWeight: '700',
    color: '#4CAF50',
  },
});

Summary

You now have a comprehensive library of layout patterns to draw from. These patterns cover the vast majority of mobile UI needs.

🎯 Pattern Categories

  • Screen Scaffolds: Basic, Header, Header+Footer, Tab Screen
  • Headers: Simple, Back+Title, Actions, Search, Logo
  • Cards: Content, Horizontal, Vertical, Stats
  • List Items: Simple, Icon, Contact, Toggle
  • Forms: Labeled Input, Sections, Complete Forms
  • Grids: 2-column, 3-column Icons, Photo Gallery, Dashboard
  • Overlays: Centered Modal, Bottom Sheet, Toast
  • States: Empty, Loading, Skeleton

Using This Reference

Bookmark this lesson. When building new screens, identify which patterns you need, copy the relevant code, and customize. Over time, consider extracting these into your own component library for even faster development.

Coming Up Next

In the next lesson, we'll explore Responsive Design Without Media Queries. You'll learn how to make your layouts adapt to different screen sizes, orientations, and device types using React Native's tools and patterns.