Skip to main content

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

  1. Create a context for theme state
  2. Define light and dark theme objects
  3. Implement a toggle function to switch themes
  4. Wrap your app with the ThemeProvider
  5. 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.

Was this article helpful?