Lesson 4 of 8

Styling & Layouts

StyleSheet, Flexbox layouts, responsive design, and platform-specific styles

Styling in React Native

React Native uses JavaScript objects for styling, not CSS. Styles are created using StyleSheet.create() for performance optimization. The layout system is based on Flexbox, with some differences from web.

StyleSheet Basics

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

function StyledComponent() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Hello World</Text>
      
      {/* Combining styles */}
      <Text style={[styles.text, styles.bold]}>Bold text</Text>
      
      {/* Conditional styles */}
      <Text style={[styles.text, isActive && styles.active]}>
        Conditional
      </Text>
      
      {/* Inline styles (avoid for performance) */}
      <Text style={{ color: 'red', fontSize: 16 }}>
        Inline style
      </Text>
    </View>
  );
}

// Always use StyleSheet.create for performance
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#fff',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 16,
  },
  text: {
    fontSize: 16,
    color: '#666',
  },
  bold: {
    fontWeight: 'bold',
  },
  active: {
    color: '#007AFF',
  },
});

🔑 Key Differences from CSS

  • • Properties are camelCase (fontSize, not font-size)
  • • Values are numbers (no 'px' needed) or strings
  • • No inheritance (except Text within Text)
  • • No cascade - styles don't flow to children
  • • Flexbox defaults to column direction

Flexbox Layout

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

// Flexbox in React Native (defaults to column)
function FlexboxExample() {
  return (
    <View style={styles.container}>
      {/* Row layout */}
      <View style={styles.row}>
        <View style={styles.box1} />
        <View style={styles.box2} />
        <View style={styles.box3} />
      </View>
      
      {/* Flex grow */}
      <View style={styles.row}>
        <View style={[styles.box, { flex: 1 }]} />
        <View style={[styles.box, { flex: 2 }]} />
        <View style={[styles.box, { flex: 1 }]} />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  row: {
    flexDirection: 'row',         // 'row' | 'column' | 'row-reverse' | 'column-reverse'
    justifyContent: 'space-between', // main axis
    alignItems: 'center',          // cross axis
    marginBottom: 20,
  },
  box: {
    height: 60,
    backgroundColor: '#3498db',
  },
  box1: { width: 60, height: 60, backgroundColor: '#e74c3c' },
  box2: { width: 60, height: 60, backgroundColor: '#2ecc71' },
  box3: { width: 60, height: 60, backgroundColor: '#9b59b6' },
});

Common Flexbox Patterns

const layoutPatterns = StyleSheet.create({
  // Center content
  centered: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  
  // Header with left and right items
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingHorizontal: 16,
    height: 56,
  },
  
  // Card with image and content
  card: {
    flexDirection: 'row',
    padding: 16,
    backgroundColor: '#fff',
    borderRadius: 8,
  },
  cardImage: {
    width: 80,
    height: 80,
    borderRadius: 8,
  },
  cardContent: {
    flex: 1,
    marginLeft: 16,
  },
  
  // Footer fixed at bottom
  footerContainer: {
    flex: 1,
    justifyContent: 'space-between',
  },
  footer: {
    padding: 16,
    backgroundColor: '#f0f0f0',
  },
  
  // Grid layout (2 columns)
  grid: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    justifyContent: 'space-between',
  },
  gridItem: {
    width: '48%',  // ~half minus gap
    marginBottom: 16,
  },
});

Responsive Design

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

// Get screen dimensions
const { width, height } = Dimensions.get('window');

// Hook for dynamic dimensions (handles rotation)
function ResponsiveComponent() {
  const { width, height } = useWindowDimensions();
  const isLandscape = width > height;
  const isTablet = width >= 768;

  return (
    <View style={[
      styles.container,
      isLandscape && styles.landscapeContainer,
      isTablet && styles.tabletContainer,
    ]}>
      <View style={[
        styles.card,
        { width: isTablet ? '45%' : '100%' }
      ]}>
        <Text>Responsive Card</Text>
      </View>
    </View>
  );
}

// Percentage-based widths
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: width * 0.05,  // 5% of screen width
  },
  landscapeContainer: {
    flexDirection: 'row',
  },
  tabletContainer: {
    paddingHorizontal: 40,
  },
  card: {
    padding: 16,
    backgroundColor: '#fff',
    borderRadius: 8,
  },
});

// Create responsive values helper
const scale = (size) => (width / 375) * size; // Base on iPhone 8
const moderateScale = (size, factor = 0.5) => 
  size + (scale(size) - size) * factor;

Platform-Specific Styles

import { Platform, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  container: {
    ...Platform.select({
      ios: {
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.25,
        shadowRadius: 4,
      },
      android: {
        elevation: 5,
      },
    }),
  },
  
  // Platform-specific values
  text: {
    fontFamily: Platform.OS === 'ios' ? 'Helvetica' : 'Roboto',
    fontSize: 16,
    paddingTop: Platform.OS === 'ios' ? 20 : 0,
  },
  
  // Status bar padding
  safeTop: {
    paddingTop: Platform.OS === 'ios' ? 44 : 24,
  },
});

// Or use Platform.select for values
const fontFamily = Platform.select({
  ios: 'San Francisco',
  android: 'Roboto',
  default: 'System',
});

Safe Area Handling

import { SafeAreaView, StyleSheet } from 'react-native';
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';

// Using SafeAreaView (basic)
function BasicSafeArea() {
  return (
    <SafeAreaView style={styles.container}>
      <Text>Content inside safe area</Text>
    </SafeAreaView>
  );
}

// Using react-native-safe-area-context (recommended)
function App() {
  return (
    <SafeAreaProvider>
      <MyScreen />
    </SafeAreaProvider>
  );
}

function MyScreen() {
  const insets = useSafeAreaInsets();
  
  return (
    <View style={[styles.container, { 
      paddingTop: insets.top,
      paddingBottom: insets.bottom,
      paddingLeft: insets.left,
      paddingRight: insets.right,
    }]}>
      <Text>Content respects notch and home indicator</Text>
    </View>
  );
}

// For bottom tabs or fixed footers
function FixedFooter() {
  const insets = useSafeAreaInsets();
  
  return (
    <View style={[styles.footer, { paddingBottom: insets.bottom }]}>
      <Button title="Submit" />
    </View>
  );
}

Common Style Properties

const commonStyles = StyleSheet.create({
  // Box model
  box: {
    width: 100,           // number or percentage string
    height: 100,
    minWidth: 50,
    maxWidth: 200,
    padding: 16,          // all sides
    paddingHorizontal: 16, // left and right
    paddingVertical: 8,   // top and bottom
    margin: 8,
    marginTop: 16,
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    borderTopLeftRadius: 16,
  },
  
  // Text styles
  text: {
    fontSize: 16,
    fontWeight: 'bold',   // 'normal', 'bold', '100'-'900'
    fontStyle: 'italic',
    color: '#333',
    textAlign: 'center',  // 'left', 'right', 'center', 'justify'
    textTransform: 'uppercase',
    letterSpacing: 1,
    lineHeight: 24,
    textDecorationLine: 'underline',
  },
  
  // Positioning
  positioned: {
    position: 'absolute', // 'relative' (default) or 'absolute'
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    zIndex: 10,
  },
  
  // Effects
  effects: {
    opacity: 0.8,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    overflow: 'hidden',   // 'visible', 'hidden', 'scroll'
  },
});

Styled Components Pattern

// Create reusable styled components

// Typography components
const Typography = {
  H1: ({ children, style }) => (
    <Text style={[typography.h1, style]}>{children}</Text>
  ),
  H2: ({ children, style }) => (
    <Text style={[typography.h2, style]}>{children}</Text>
  ),
  Body: ({ children, style }) => (
    <Text style={[typography.body, style]}>{children}</Text>
  ),
};

const typography = StyleSheet.create({
  h1: { fontSize: 32, fontWeight: 'bold', color: '#111' },
  h2: { fontSize: 24, fontWeight: '600', color: '#333' },
  body: { fontSize: 16, lineHeight: 24, color: '#666' },
});

// Container components
const Container = ({ children, style }) => (
  <View style={[containers.default, style]}>{children}</View>
);

const Card = ({ children, style }) => (
  <View style={[containers.card, style]}>{children}</View>
);

const containers = StyleSheet.create({
  default: { flex: 1, padding: 16 },
  card: {
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 16,
    ...Platform.select({
      ios: { shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 10 },
      android: { elevation: 3 },
    }),
  },
});

// Usage
<Container>
  <Typography.H1>Welcome</Typography.H1>
  <Card>
    <Typography.Body>Card content here</Typography.Body>
  </Card>
</Container>