Implementing Dark Mode
1. Detecting System Color Scheme
| Method | Description | Notes |
|---|---|---|
| Appearance.isActive | Boolean indicating if dark mode is active | Read-only property |
| Appearance.getColorScheme() | Returns "light" or "dark" | Available since React Native 0.63 |
Example: Detecting System Color Scheme
import { Appearance } from 'react-native';\n\nconst colorScheme = Appearance.getColorScheme();\nconsole.log(colorScheme); // 'light' or 'dark'
Note: The
getColorScheme method returns the system's current color scheme. It does not update automatically when the user changes their preference.
2. Creating Theme Context
Example: Basic Theme Context Setup
// themes.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');
};
const themeData = {
mode: theme,
toggleTheme
};
return (
<ThemeContext.Provider value={themeData}>
{children}
</ThemeContext.Provider>
);
};
| Property | Description | Values |
|---|---|---|
| mode | Current theme mode | 'light' or 'dark' |
| toggleTheme | Function to switch between themes | Function |
Note: The ThemeContext is created using React's built-in
createContext API.
Warning: Always wrap your component tree with
ThemeProvider to ensure useTheme works correctly.
3. Implementing Theme Provider
Example: Basic Theme Provider Setup
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 Value |
|---|---|---|
| theme | Current theme name (light or dark) | 'light' |
| toggleTheme | Function to switch between themes | undefined |
Note: The ThemeContext is used to share theme state across components without prop drilling.
Example: Using Theme in Components
import React from 'react';
import { useTheme } from './ThemeProvider';
const ThemeComponent = () => {
const { theme, toggleTheme } = useTheme();
return (
<div style={{ backgroundColor: theme === 'dark' ? '#333' : '#fff', color: theme === 'dark' ? '#fff' : '#000' }}>
<h1>Current Theme: {theme}</h1>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
};
export default ThemeComponent;
4. Styling Components for Dark Mode
| Property | Values | Description |
|---|---|---|
| backgroundColor | '#000' or 'black' |
Sets the background color to dark theme |
| color | '#fff' or 'white' |
Sets the text color to light theme |
| tintColor | '#fff' or 'white' |
Applies to images and icons for dark mode |
Example: Using StyleSheet with Dark Mode
import { StyleSheet, View } from 'react-native';\n\nconst styles = StyleSheet.create({\n container: {\n backgroundColor: '#000',\n padding: 20\n },\n text: {\n color: '#fff',\n fontSize: 16\n }\n});
Example: Conditional Styling Based on Theme
const isDarkMode = useColorScheme() === 'dark';\n\nconst themeStyles = StyleSheet.create({\n background: {\n backgroundColor: isDarkMode ? '#000' : '#fff'\n },\n text: {\n color: isDarkMode ? '#fff' : '#000'\n }\n});
Note: Use
useColorScheme() hook to detect the user's preferred color scheme.
Warning: Avoid hardcoding colors; use theme variables or dynamic values for better maintainability.
5. Using Dynamic Colors
| Property | Description | Usage |
|---|---|---|
| dynamicColor | Applies a dynamic color that adapts to system settings | color: dynamicColor('systemBlue'); |
| dynamicColor | Supports light and dark mode variants | color: dynamicColor('systemBlue', 'systemBlueDark'); |
Note: Dynamic colors are supported on iOS 13+ and Android 10+.
6. Persisting Theme Preferences
Example: Using AsyncStorage to Save Theme Preference
import AsyncStorage from '@react-native-async-storage/async-storage';
const saveThemePreference = async (theme) => {
try {
await AsyncStorage.setItem('user-theme', theme);
} catch (error) {
console.error('Failed to save theme preference:', error);
}
};
const loadThemePreference = async () => {
try {
const theme = await AsyncStorage.getItem('user-theme');
return theme || 'light';
} catch (error) {
console.error('Failed to load theme preference:', error);
return 'light';
}
};
| Method | Description | Notes |
|---|---|---|
| AsyncStorage.setItem(key, value) | Saves a key-value pair to storage | Asynchronous operation |
| AsyncStorage.getItem(key) | Retrieves a value by key | Returns a promise resolving to the value or null |
Note: AsyncStorage is not available in Expo Go. For Expo projects, use
expo-secure-store or expo-local-storage for secure storage.
Warning: Never store sensitive data like passwords or tokens in AsyncStorage. Use
expo-secure-store for such cases.
Best Practices for Theme Persistence
- Always provide a fallback theme (e.g., 'light') if no preference is found
- Use try-catch blocks to handle potential errors during storage operations
- Consider using a state management solution like Redux or Context API to manage theme state globally
- Implement a loading indicator while fetching theme preferences on app startup
7. Creating Theme Toggle Component
Example: Basic Theme Toggle Component
import React, { useState } from 'react';
import { View, TouchableOpacity, Text } from 'react-native';
const ThemeToggle = ({ theme, toggleTheme }) => {
return (
<View style={{ padding: 10 }}>
<TouchableOpacity onPress={toggleTheme}>
<Text style={{ fontSize: 16 }}>
Switch to {theme === 'dark' ? 'light' : 'dark'} mode
</Text>
</TouchableOpacity>
</View>
);
};
export default ThemeToggle;
| Property | Description | Default |
|---|---|---|
| theme | Current theme: 'light' or 'dark' | 'light' |
| toggleTheme | Function to switch themes | - |
Note: The component should be integrated with a global theme context or state manager to persist the selected theme across app sessions.
8. Handling StatusBar in Dark Mode
Example: Setting StatusBar Style
import { StatusBar } from 'react-native';
<StatusBar
barStyle="light-content"
backgroundColor="#000000"
/>
| Property | Values | Description |
|---|---|---|
| barStyle | "default" | "light-content" | "dark-content" |
Style of the status bar text and icons |
| backgroundColor | string |
Background color of the status bar (hex, rgb, etc.) |
| hidden | true | false |
Whether to hide the status bar |
| translucent | true | false |
Whether the status bar is translucent |
Note: On iOS,
barStyle affects the color of status bar text and icons. On Android, it controls the contrast between the status bar and content.
9. Testing Dark Mode Implementation
Example: Testing with Jest and React Native Testing Library
import { render } from '@testing-library/react-native';
import { ThemeProvider } from 'react-native-paper';
import { DarkTheme, DefaultTheme } from '@react-navigation/native';
import { useTheme } from 'react-native-paper';
// Mock the theme context
jest.mock('react-native-paper', () => ({
...jest.requireActual('react-native-paper'),
useTheme: jest.fn(),
}));
const TestComponent = () => {
const { colors } = useTheme();
return <View style={{ backgroundColor: colors.background }}><Text>Test</Text></View>;
};
describe('Dark Mode Tests', () => {
it('renders with dark theme colors', () => {
const theme = DarkTheme;
jest.spyOn(require('react-native-paper'), 'useTheme').mockReturnValue(theme);
const { getByText } = render(<ThemeProvider theme={theme}><TestComponent /></ThemeProvider>);
expect(getByText('Test')).toBeTruthy();
});
});
| Test Type | Description | Tools |
|---|---|---|
| Visual Regression | Ensures UI matches expected dark mode appearance across devices | jest-image-snapshot, detox |
| Theme Context | Verifies correct theme is applied via context providers | react-native-testing-library |
| Color Contrast | Validates WCAG contrast ratios for accessibility | jest-axe, react-native-accessibility |
Testing Workflow
- Set up test environment with
jestandreact-native-testing-library - Create mock theme provider for both light and dark themes
- Render component under test with each theme context
- Assert that UI elements use correct color values from theme
- Run visual regression tests to ensure no layout shifts
Best Practices for Testing
- Use
react-native-paperorstyled-componentstheme context in tests - Mock theme providers to isolate component behavior
- Test both light and dark themes for consistency
- Validate color contrast ratios using accessibility tools
- Automate visual regression tests for cross-platform compatibility
10. Using Styled System for Theming
Example: Defining a Theme with Styled System
import styled from 'styled-components/native';
import { ThemeProvider } from 'styled-components';
const theme = {
colors: {
primary: '#007AFF',
secondary: '#FF9500',
background: '#FFFFFF',
text: '#000000',
},
fonts: {
regular: 'System',
bold: 'System-Bold',
},
};
const StyledText = styled.Text<{ color: props => props.theme.colors.text }>;
const StyledView = styled.View<{ backgroundColor: props => props.theme.colors.background }>;
export { theme, StyledText, StyledView };
| Property | Description | Values |
|---|---|---|
| colors.primary | Primary brand color | '#007AFF' |
| colors.secondary | Secondary brand color | '#FF9500' |
| colors.background | Background color for views | '#FFFFFF' |
| colors.text | Text color for labels | '#000000' |
| fonts.regular | Regular font family | 'System' |
| fonts.bold | Bold font family | 'System-Bold' |
Note: Styled System is a library that provides a theme-based approach to styling in React Native.
Warning: Ensure that all color values are accessible and meet WCAG contrast ratios for readability.