Implementing Theming and Dark Mode
1. Creating Theme Context
Example: Basic Theme Context Setup
// themeContext.js
import React, { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
| Property | Description | Default |
|---|---|---|
| theme | Current theme mode ('light' or 'dark') | 'light' |
| toggleTheme | Function to switch between themes | - |
Note: The theme context is typically wrapped around the root component of your app to make it available throughout the component tree.
2. Using Appearance API
| Property | Description | Values |
|---|---|---|
| colorScheme | Indicates the user's preferred color scheme | "light" or "dark" |
Example: Detecting Color Scheme
import { Appearance } from 'react-native';\n\nconst colorScheme = Appearance.getColorScheme();\nconsole.log(colorScheme); // "light" or "dark"
Note: The
Appearance API is part of React Native and works across iOS and Android.
Example: Listening to Color Scheme Changes
import { Appearance } from 'react-native';\n\nconst handleChange = (newColorScheme) => {\n console.log('Color scheme changed to:', newColorScheme);\n};\n\nconst subscription = Appearance.addChangeListener(handleChange);\n\n// Don't forget to remove the listener when component unmounts\nsubscription.remove();
3. Detecting Color Scheme
| Method | Description | Return Value | Notes |
|---|---|---|---|
| useColorScheme() | Hook to detect the user's preferred color scheme | 'light' or 'dark' |
Available in Expo SDK 49+ |
Example: Using useColorScheme
import { useColorScheme } from 'expo-system-ui';\n\nconst MyComponent = () => {\n const colorScheme = useColorScheme();\n \n return (\n <View style={{\n backgroundColor: colorScheme === 'dark' ? '#000' : '#fff'\n }}>\n <Text>Current scheme: {colorScheme}</Text>\n </View>\n );\n};
Note: The
useColorScheme hook returns the system's color scheme preference, which can be either 'light' or 'dark'. It does not reflect the actual theme applied to your app, but rather the user's system preference.
4. Implementing Dark Mode
| Property | Description | Values |
|---|---|---|
| theme | Theme configuration object | { colors: { primary: '#000', background: '#fff' } } |
| darkMode | Enables or disables dark mode | true or false |
Example: Using Dark Mode in Expo
import { useColorScheme } from 'react-native';\nimport { ThemeProvider, useTheme } from '@react-navigation/native';\n\nconst App = () => {\n const colorScheme = useColorScheme();\n const theme = {\n dark: colorScheme === 'dark',\n colors: {\n primary: '#000',\n background: colorScheme === 'dark' ? '#111' : '#fff',\n }\n };\n\n return (\n <ThemeProvider theme={theme}>\n <MainApp />\n </ThemeProvider>\n );\n};
Note: Expo provides built-in support for dark mode through
useColorScheme which detects the system's theme preference.
Warning: Ensure that all UI elements have appropriate contrast in both light and dark modes to maintain accessibility.
5. Setting Status Bar Style
| Property | Values | Description |
|---|---|---|
| barStyle | "light-content", "dark-content" |
Defines the color of status bar text. NEW in Expo SDK 50 |
| backgroundColor | "#FFFFFF", "transparent" |
Background color of the status bar. Defaults to transparent. |
Example: Setting Status Bar Style
import { StatusBar } from 'expo-status-bar';
export default function App() {
return (
<View style={{ flex: 1 }}>
<StatusBar
barStyle="dark-content"
backgroundColor="#ffffff"
/>
<Text>Hello, Expo!</Text>
</View>
);
}
Note: On iOS,
barStyle only affects the status bar text color. On Android, it also changes the background color.
6. Using Dynamic Colors
| Property | Description | Values | Notes |
|---|---|---|---|
| dynamicColor | Applies dynamic color to components | true / false |
Enables or disables dynamic color support |
| colorScheme | Defines the color scheme to use | light, dark |
Overrides system color scheme |
Example: Applying Dynamic Colors
import { dynamicColor } from 'expo-theme';
const theme = {
backgroundColor: dynamicColor({
light: '#ffffff',
dark: '#000000'
}),
textColor: dynamicColor({
light: '#000000',
dark: '#ffffff'
})
};
export default theme;
Note: Dynamic colors require Expo SDK 50 or higher.
7. Persisting Theme Preference
Example: Using AsyncStorage to Save Theme
import AsyncStorage from '@react-native-async-storage/async-storage';
const THEME_KEY = 'user-theme';
export const saveThemePreference = async (theme) => {
try {
await AsyncStorage.setItem(THEME_KEY, theme);
} catch (error) {
console.error('Failed to save theme preference', error);
}
};
export const loadThemePreference = async () => {
try {
const theme = await AsyncStorage.getItem(THEME_KEY);
return theme || 'light';
} catch (error) {
console.error('Failed to load theme preference', error);
return 'light';
}
};
| Method | Description | Notes |
|---|---|---|
| saveThemePreference(theme) | Saves the selected theme to storage | Theme can be 'light', 'dark', or custom |
| loadThemePreference() | Loads the previously saved theme | Returns default 'light' if none found |
Note: Ensure
@react-native-async-storage/async-storage is installed and linked for native platforms.
Warning: On web,
AsyncStorage uses localStorage, which may be cleared by users or browsers.
8. Creating Theme Provider
Example: Basic Theme Provider Setup
// App.js
import React, { createContext, useContext, useState } from 'react';
import { View, Text, TouchableOpacity } from 'expo-views';
const ThemeContext = createContext();
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};
export const ThemeProvider = ({ children }) => {
const [isDarkMode, setIsDarkMode] = useState(false);
const toggleTheme = () => {
setIsDarkMode(!isDarkMode);
};
const theme = isDarkMode ? darkTheme : lightTheme;
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
const lightTheme = {
background: '#ffffff',
text: '#000000',
buttonBg: '#e0e0e0',
buttonText: '#000000',
};
const darkTheme = {
background: '#1a1a1a',
text: '#ffffff',
buttonBg: '#333333',
buttonText: '#ffffff',
};
| Property | Description | Values |
|---|---|---|
| ThemeProvider | Context provider for theme state | Wraps app components to provide theme context |
| useTheme | Custom hook to access theme values | Returns current theme and toggle function |
| isDarkMode | Boolean flag for theme mode | true / false |
| toggleTheme | Function to switch between themes | Toggles isDarkMode state |
Note: ThemeProvider should wrap your root component to make theme values available throughout the app.
Workflow: Setting Up Theme Provider
- Create a context for theme state
- Define light and dark theme objects
- Implement a toggle function to switch themes
- Wrap your app with the ThemeProvider
- Use
useTheme()in components to access theme values
9. Using Theme Variables
| Variable | Type | Description | Default Value |
|---|---|---|---|
| colors.primary | string |
Primary color for the theme | #007AFF |
| colors.secondary | string |
Secondary color for the theme | #34C759 |
| colors.background | string |
Background color for the theme | #FFFFFF |
| colors.text | string |
Text color for the theme | #000000 |
| colors.border | string |
Border color for the theme | #E0E0E0 |
Example: Defining Theme Variables
const theme = {
colors: {
primary: '#007AFF',
secondary: '#34C759',
background: '#FFFFFF',
text: '#000000',
border: '#E0E0E0'
}
};
Example: Using Theme Variables in Components
import { StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
backgroundColor: theme.colors.background,
padding: 20
},
text: {
color: theme.colors.text,
fontSize: 16
}
});
10. Implementing Theme Switching
Example: Basic Theme Switching Setup
{ "theme": "light", "colors": { "primary": "#007AFF", "background": "#FFFFFF" } }
| Property | Value | Description |
|---|---|---|
| theme | light | dark |
Current theme mode |
| colors.primary | #007AFF |
Primary color for UI elements |
| colors.background | #FFFFFF |
Background color for the app |
Note: Theme switching is typically handled via context or state management libraries like React Context or Zustand.
Example: Using React Context for Theme
import React, { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be used within a ThemeProvider');
return context;
};
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
Example: Applying Theme in Components
import { useTheme } from './ThemeProvider';
const ThemedButton = () => {
const { theme } = useTheme();
const backgroundColor = theme === 'dark' ? '#1C1C1E' : '#FFFFFF';
return (
<button style={{ backgroundColor }}>
Click me
</button>
);
};
11. Supporting System Theme Changes
| Property | Description | Values | Notes |
|---|---|---|---|
| useColorScheme() | Hook to detect system theme | 'light', 'dark' |
Available in Expo SDK 49+ |
Example: Detecting System Theme
import { useColorScheme } from 'expo-system-ui';\n\nconst theme = useColorScheme();\nconsole.log(theme); // 'light' or 'dark'
Note: The
useColorScheme hook returns the system theme preference and updates automatically when the user changes their system settings.
Key Points
- NEW Introduced in Expo SDK 49
- Automatically updates when system theme changes
- Useful for implementing dynamic dark/light mode
12. Using Themed Components
| Component | Props | Description | Example |
|---|---|---|---|
| ThemedText | variant, color |
Text component that respects theme settings | <ThemedText variant="title1">Hello World</ThemedText> |
| ThemedView | style, backgroundColor |
View component that adapts to theme background | <ThemedView style={{ padding: 10 }}></ThemedView> |
| ThemedButton | title, onPress |
Button that changes appearance based on theme | <ThemedButton title="Press me" onPress={() => {}} /> |
Example: ThemedText Usage
import { ThemedText } from 'expo/themes';\n\n<ThemedText variant="body">This text adapts to theme</ThemedText>
Example: ThemedView Usage
import { ThemedView } from 'expo/themes';\n\n<ThemedView style={{ padding: 20 }}>\n <ThemedText>Content inside themed view</ThemedText>\n</ThemedView>
Note: Themed components automatically adjust to light/dark mode based on
useColorScheme.