React Native Design
Master React Native styling patterns, React Navigation, and Reanimated 3 to build performant, cross-platform mobile applications with native-quality user experiences.
When to Use This Skill
-
Building cross-platform mobile apps with React Native
-
Implementing navigation with React Navigation 6+
-
Creating performant animations with Reanimated 3
-
Styling components with StyleSheet and styled-components
-
Building responsive layouts for different screen sizes
-
Implementing platform-specific designs (iOS/Android)
-
Creating gesture-driven interactions with Gesture Handler
-
Optimizing React Native performance
Core Concepts
- StyleSheet and Styling
Basic StyleSheet:
import { StyleSheet, View, Text } from 'react-native';
const styles = StyleSheet.create({ container: { flex: 1, padding: 16, backgroundColor: '#ffffff', }, title: { fontSize: 24, fontWeight: '600', color: '#1a1a1a', marginBottom: 8, }, subtitle: { fontSize: 16, color: '#666666', lineHeight: 24, }, });
function Card() { return ( <View style={styles.container}> <Text style={styles.title}>Title</Text> <Text style={styles.subtitle}>Subtitle text</Text> </View> ); }
Dynamic Styles:
interface CardProps { variant: 'primary' | 'secondary'; disabled?: boolean; }
function Card({ variant, disabled }: CardProps) { return ( <View style={[ styles.card, variant === 'primary' ? styles.primary : styles.secondary, disabled && styles.disabled, ]}> <Text style={styles.text}>Content</Text> </View> ); }
const styles = StyleSheet.create({ card: { padding: 16, borderRadius: 12, }, primary: { backgroundColor: '#6366f1', }, secondary: { backgroundColor: '#f3f4f6', borderWidth: 1, borderColor: '#e5e7eb', }, disabled: { opacity: 0.5, }, text: { fontSize: 16, }, });
- Flexbox Layout
Row and Column Layouts:
const styles = StyleSheet.create({ // Vertical stack (column) column: { flexDirection: "column", gap: 12, }, // Horizontal stack (row) row: { flexDirection: "row", alignItems: "center", gap: 8, }, // Space between items spaceBetween: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", }, // Centered content centered: { flex: 1, justifyContent: "center", alignItems: "center", }, // Fill remaining space fill: { flex: 1, }, });
- React Navigation Setup
Stack Navigator:
import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack';
type RootStackParamList = { Home: undefined; Detail: { itemId: string }; Settings: undefined; };
const Stack = createNativeStackNavigator<RootStackParamList>();
function AppNavigator() {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerStyle: { backgroundColor: '#6366f1' },
headerTintColor: '#ffffff',
headerTitleStyle: { fontWeight: '600' },
}}
>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'Home' }}
/>
<Stack.Screen
name="Detail"
component={DetailScreen}
options={({ route }) => ({
title: Item ${route.params.itemId},
})}
/>
<Stack.Screen name="Settings" component={SettingsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
Tab Navigator:
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { Ionicons } from '@expo/vector-icons';
type TabParamList = { Home: undefined; Search: undefined; Profile: undefined; };
const Tab = createBottomTabNavigator<TabParamList>();
function TabNavigator() { return ( <Tab.Navigator screenOptions={({ route }) => ({ tabBarIcon: ({ focused, color, size }) => { const icons: Record<string, keyof typeof Ionicons.glyphMap> = { Home: focused ? 'home' : 'home-outline', Search: focused ? 'search' : 'search-outline', Profile: focused ? 'person' : 'person-outline', }; return <Ionicons name={icons[route.name]} size={size} color={color} />; }, tabBarActiveTintColor: '#6366f1', tabBarInactiveTintColor: '#9ca3af', })} > <Tab.Screen name="Home" component={HomeScreen} /> <Tab.Screen name="Search" component={SearchScreen} /> <Tab.Screen name="Profile" component={ProfileScreen} /> </Tab.Navigator> ); }
- Reanimated 3 Basics
Animated Values:
import Animated, { useSharedValue, useAnimatedStyle, withSpring, withTiming, } from 'react-native-reanimated';
function AnimatedBox() { const scale = useSharedValue(1); const opacity = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], opacity: opacity.value, }));
const handlePress = () => { scale.value = withSpring(1.2, {}, () => { scale.value = withSpring(1); }); };
return ( <Pressable onPress={handlePress}> <Animated.View style={[styles.box, animatedStyle]} /> </Pressable> ); }
Gesture Handler Integration:
import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated, { useSharedValue, useAnimatedStyle, withSpring, } from 'react-native-reanimated';
function DraggableCard() { const translateX = useSharedValue(0); const translateY = useSharedValue(0);
const gesture = Gesture.Pan() .onUpdate((event) => { translateX.value = event.translationX; translateY.value = event.translationY; }) .onEnd(() => { translateX.value = withSpring(0); translateY.value = withSpring(0); });
const animatedStyle = useAnimatedStyle(() => ({ transform: [ { translateX: translateX.value }, { translateY: translateY.value }, ], }));
return ( <GestureDetector gesture={gesture}> <Animated.View style={[styles.card, animatedStyle]}> <Text>Drag me!</Text> </Animated.View> </GestureDetector> ); }
- Platform-Specific Styling
import { Platform, StyleSheet } from "react-native";
const styles = StyleSheet.create({ container: { padding: 16, ...Platform.select({ ios: { shadowColor: "#000", shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, }, android: { elevation: 4, }, }), }, text: { fontFamily: Platform.OS === "ios" ? "SF Pro Text" : "Roboto", fontSize: 16, }, });
// Platform-specific components import { Platform } from "react-native"; const StatusBarHeight = Platform.OS === "ios" ? 44 : 0;
Quick Start Component
import React from 'react'; import { View, Text, StyleSheet, Pressable, Image, } from 'react-native'; import Animated, { useSharedValue, useAnimatedStyle, withSpring, } from 'react-native-reanimated';
interface ItemCardProps { title: string; subtitle: string; imageUrl: string; onPress: () => void; }
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
export function ItemCard({ title, subtitle, imageUrl, onPress }: ItemCardProps) { const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], }));
return ( <AnimatedPressable style={[styles.card, animatedStyle]} onPress={onPress} onPressIn={() => { scale.value = withSpring(0.97); }} onPressOut={() => { scale.value = withSpring(1); }} > <Image source={{ uri: imageUrl }} style={styles.image} /> <View style={styles.content}> <Text style={styles.title} numberOfLines={1}> {title} </Text> <Text style={styles.subtitle} numberOfLines={2}> {subtitle} </Text> </View> </AnimatedPressable> ); }
const styles = StyleSheet.create({ card: { backgroundColor: '#ffffff', borderRadius: 16, overflow: 'hidden', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 4, }, image: { width: '100%', height: 160, backgroundColor: '#f3f4f6', }, content: { padding: 16, gap: 4, }, title: { fontSize: 18, fontWeight: '600', color: '#1f2937', }, subtitle: { fontSize: 14, color: '#6b7280', lineHeight: 20, }, });
Best Practices
-
Use TypeScript: Define navigation and prop types for type safety
-
Memoize Components: Use React.memo and useCallback to prevent unnecessary rerenders
-
Run Animations on UI Thread: Use Reanimated worklets for 60fps animations
-
Avoid Inline Styles: Use StyleSheet.create for performance
-
Handle Safe Areas: Use SafeAreaView or useSafeAreaInsets
-
Test on Real Devices: Simulator/emulator performance differs from real devices
-
Use FlatList for Lists: Never use ScrollView with map for long lists
-
Platform-Specific Code: Use Platform.select for iOS/Android differences
Common Issues
-
Gesture Conflicts: Wrap gestures with GestureDetector and use simultaneousHandlers
-
Navigation Type Errors: Define ParamList types for all navigators
-
Animation Jank: Move animations to UI thread with runOnUI worklets
-
Memory Leaks: Cancel animations and cleanup in useEffect
-
Font Loading: Use expo-font or react-native-asset for custom fonts
-
Safe Area Issues: Test on notched devices (iPhone, Android with cutouts)