Skip to main content

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');

Example: Applying Dynamic Colors

import { dynamicColor } from 'react-native'
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

  1. Set up test environment with jest and react-native-testing-library
  2. Create mock theme provider for both light and dark themes
  3. Render component under test with each theme context
  4. Assert that UI elements use correct color values from theme
  5. Run visual regression tests to ensure no layout shifts

Best Practices for Testing

  • Use react-native-paper or styled-components theme 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.

Key Points

  • Styled System allows for consistent theming across components.
  • Themes can be defined with color, font, and spacing scales.
  • Components can access theme values using props.theme.

Was this article helpful?