Introduction
Custom headers are essential for creating a unique and branded mobile experience. With Expo SDK 51 and React Navigation 6, we have more powerful tools than ever to create beautiful, interactive headers. In this comprehensive guide, we'll explore everything from basic customization to advanced patterns.
Prerequisites
Before we dive in, make sure you have the following set up:
npx create-expo-app my-header-app
cd my-header-app
npx expo install @react-navigation/native @react-navigation/native-stack
Basic Header Customization
Let's start with the fundamentals of header customization:
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { StyleSheet } from 'react-native';
type RootStackParamList = {
Home: undefined;
Details: { itemId: number };
};
const Stack = createNativeStackNavigator
<RootStackParamList>();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerStyle: styles.header,
headerTintColor: '#fff',
headerTitleStyle: styles.headerTitle,
}}
>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
title: 'My App',
}}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
const styles = StyleSheet.create({
header: {
backgroundColor: '#6200ee',
},
headerTitle: {
fontWeight: 'bold',
},
});
Advanced Header Patterns
Custom Header Component
Here's how to create a completely custom header:
import { Image, Pressable, View } from "react-native";
interface CustomHeaderProps {
title: string;
onMenuPress: () => void;
onSearchPress: () => void;
}
function CustomHeader({
title,
onMenuPress,
onSearchPress,
}: CustomHeaderProps) {
return (
<View style={styles.headerContainer}>
<Pressable onPress={onMenuPress} style={styles.iconButton}>
<Image source={require("./assets/menu-icon.png")} style={styles.icon} />
</Pressable>
<Text style={styles.headerTitle}>{title}</Text>
<Pressable onPress={onSearchPress} style={styles.iconButton}>
<Image
source={require("./assets/search-icon.png")}
style={styles.icon}
/>
</Pressable>
</View>
);
}
// Implementation in navigation
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
header: ({ navigation, route, options })=> (
<CustomHeader
title="Home"
onMenuPress={()=> navigation.openDrawer()}
onSearchPress={()=> navigation.navigate("Search")}
/>
),
}}
/>;
Animated Headers
Here's an example of a collapsible header:
import Animated, {
useAnimatedScrollHandler,
useAnimatedStyle,
useSharedValue,
withSpring,
} from "react-native-reanimated";
function AnimatedHeader() {
const scrollY = useSharedValue(0);
const scrollHandler = useAnimatedScrollHandler({
onScroll: (event) => {
scrollY.value = event.contentOffset.y;
},
});
const headerStyle = useAnimatedStyle(() => {
return {
height: withSpring(Math.max(45, 90 - scrollY.value), {
damping: 20,
stiffness: 90,
}),
};
});
return (
<View style={styles.container}>
<Animated.View style={[styles.header, headerStyle]}>
<Text style={styles.headerText}>Animated Header</Text>
</Animated.View>
<Animated.ScrollView onScroll={scrollHandler} scrollEventThrottle={16}>
{/* Content */}
</Animated.ScrollView>
</View>
);
}
Handling Platform Differences
Different platforms have different header conventions. Here's how to handle them:
import { Platform } from "react-native";
const screenOptions = {
headerStyle: {
height: Platform.select({
ios: 110,
android: 90,
}),
...Platform.select({
ios: {
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
},
android: {
elevation: 4,
},
}),
},
};
Performance Optimization
Memoization
Always memoize your custom header components:
import { memo, useCallback } from "react";
const MemoizedHeader = memo(function Header({ title, onPress }) {
const handlePress = useCallback(() => {
onPress();
}, [onPress]);
return (
<Pressable onPress={handlePress}>
<Text>{title}</Text>
</Pressable>
);
});
Layout Animation
For smooth transitions:
import { LayoutAnimation } from "react-native";
function updateHeaderHeight() {
LayoutAnimation.configureNext(
LayoutAnimation.create(
300,
LayoutAnimation.Types.easeInEaseOut,
LayoutAnimation.Properties.opacity,
),
);
setHeaderExpanded(!headerExpanded);
}
Common Pitfalls
- Safe Area Handling
import { SafeAreaView } from "react-native-safe-area-context";
function SafeHeader() {
return (
<SafeAreaView edges={["top"]} style={styles.safeArea}>
<View style={styles.headerContent}>{/* Header content */}</View>
</SafeAreaView>
);
}
- Status Bar Management
import { StatusBar } from "expo-status-bar";
function Header() {
return (
<View style={styles.header}>
<StatusBar style="light" />
{/* Header content */}
</View>
);
}
Conclusion
Creating custom headers in Expo SDK 51 offers unlimited possibilities for customization. Remember to:
- Consider platform-specific behaviors
- Optimize performance through memoization
- Handle safe areas and status bars appropriately
- Test thoroughly on both iOS and Android
Resources
For the thumbnail, I recommend creating an image with:
- A mobile phone frame in the center
- A prominently displayed custom header with gradient background
- Some floating UI elements showing different header variations
- React Navigation's purple color scheme (#6200ee) as the primary color
- Dimensions: 1200x630px (optimal for social media sharing)
You can create this using design tools like Figma or Adobe XD, or commission it from a designer for the best results.