Skip to main content
React Native Introduction
CHAPTER 28 Beginner

Theming and Dark Mode

Updated: May 16, 2026
7 min read

# CHAPTER 28

Theming and Dark Mode

1. Introduction

In modern mobile development, "Dark Mode" is no longer an optional feature; it is an absolute user expectation. If a user opens your app in a dark bedroom and is blinded by a pure white screen, they will uninstall it. Building a Theme System requires architectural forethought. You cannot simply hardcode backgroundColor: 'white' into 50 different components. In this chapter, we will master Theming and Dark Mode. We will learn to detect the device's system-level color preferences using the useColorScheme hook, and build a dynamic Context Provider to inject theme variables globally into our style sheets.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Detect OS-level Dark/Light mode settings.
  • Utilize the React Native useColorScheme hook.
  • Define a strict global Color Palette object.
  • Integrate Theme variables into the StyleSheet API.
  • Build a toggle system using the Context API to override system preferences.

3. Detecting the System Theme

Both iOS and Android have a system-wide Dark Mode toggle. React Native provides the useColorScheme hook, which automatically reads this setting and returns either the string 'light' or 'dark'. Crucially, if the user pulls down their notification center and changes the system theme, this hook immediately triggers a re-render!
javascript
1234567891011121314151617181920
import React from 'react';
import { View, Text, useColorScheme } from 'react-native';

export default function BasicThemeScreen() {
  // 1. Detect the system theme!
  const theme = useColorScheme(); 
  
  // 2. Define colors dynamically based on the string
  const isDark = theme === 'dark';
  const bgColor = isDark ? '#121212' : '#FFFFFF';
  const textColor = isDark ? '#FFFFFF' : '#000000';

  return (
    <View style={{ flex: 1, backgroundColor: bgColor, justifyContent: &#039;center&#039; }}>
      <Text style={{ color: textColor }}>
        The current system theme is {theme}!
      </Text>
    </View>
  );
}

4. Architecting a Global Color Palette

Writing ternary operators (isDark ? 'black' : 'white') in every component is messy. Professionals create a central colors.js file to define strict design tokens.
javascript
123456789101112131415
// themes/colors.js
export const Colors = {
  light: {
    background: &#039;#FFFFFF&#039;,
    text: &#039;#111827&#039;,
    primary: &#039;#3B82F6&#039;, // Blue
    card: &#039;#F3F4F6&#039;
  },
  dark: {
    background: &#039;#121212&#039;,
    text: &#039;#F9FAFB&#039;,
    primary: &#039;#60A5FA&#039;, // Lighter Blue for dark mode contrast
    card: &#039;#1F2937&#039;
  }
};

5. Applying the Palette (Inline vs StyleSheet)

To use this palette, you fetch the current theme string and grab the corresponding object.
javascript
123456789101112131415
import React from &#039;react&#039;;
import { View, Text, useColorScheme } from &#039;react-native&#039;;
import { Colors } from &#039;./themes/colors&#039;;

export default function ProfileCard() {
  const theme = useColorScheme() ?? &#039;light&#039;; // Fallback to light
  const currentColors = Colors[theme]; // Grabs the light or dark object!

  return (
    // INLINE APPLICATION:
    <View style={{ backgroundColor: currentColors.background }}>
      <Text style={{ color: currentColors.text }}>John Doe</Text>
    </View>
  );
}

6. The "User Override" Problem

The useColorScheme hook is great, but what if the user's phone is in Light Mode, but they explicitly want YOUR app to be in Dark Mode? We cannot rely solely on the OS. We must build a ThemeContext (combining what we learned in Chapter 16 and Chapter 20).

The ThemeContext Flow:

  1. 1. Check AsyncStorage to see if the user saved a preference (e.g., 'dark').
  1. 2. If no saved preference, fall back to useColorScheme().
  1. 3. Provide a toggleTheme function that updates the Context and saves the new choice to the hard drive!

7. Building the Advanced ThemeProvider

javascript
123456789101112131415161718192021222324252627282930313233343536
import React, { createContext, useState, useEffect } from &#039;react&#039;;
import { useColorScheme } from &#039;react-native&#039;;
import AsyncStorage from &#039;@react-native-async-storage/async-storage&#039;;
import { Colors } from &#039;./colors&#039;;

export const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
  const systemTheme = useColorScheme();
  
  // State holds 'light' or 'dark'
  const [themeMode, setThemeMode] = useState(systemTheme || &#039;light&#039;);

  // Load user preference on boot
  useEffect(() => {
    const loadTheme = async () => {
      const savedTheme = await AsyncStorage.getItem(&#039;@theme&#039;);
      if (savedTheme) setThemeMode(savedTheme);
    };
    loadTheme();
  }, []);

  // Toggle function
  const toggleTheme = async () => {
    const newTheme = themeMode === &#039;light&#039; ? &#039;dark&#039; : &#039;light&#039;;
    setThemeMode(newTheme);
    await AsyncStorage.setItem(&#039;@theme&#039;, newTheme); // Save to disk!
  };

  // Provide the exact color object and the toggle function
  return (
    <ThemeContext.Provider value={{ colors: Colors[themeMode], toggleTheme, isDark: themeMode === &#039;dark&#039; }}>
      {children}
    </ThemeContext.Provider>
  );
};

8. Visual Learning: The Theme Hierarchy

txt
123456789
1. [ User Click: &#039;Switch to Dark Mode' ]
       |
2. [ ThemeContext: setThemeMode(&#039;dark') ] --> (Saves to Disk)
       |
3. [ Context Provider Updates ]
       |
4. [ Every Screen using useContext(ThemeContext) Re-Renders! ]
       |
5. [ UI paints with Colors.dark object ]

9. Common Mistakes

  • Applying Theme Variables Outside Components: If you define const styles = StyleSheet.create({ text: { color: currentColors.text } }) OUTSIDE of your React component function block, the theme will NEVER update. The StyleSheet.create executes exactly once when the file loads. To make stylesheets dynamic, you must generate them *inside* the component, or pass the theme into a custom style generator function.

10. Best Practices

  • React Navigation Theming: React Navigation has its own massive white header bars. If your app is in dark mode, but the top Navigation header is bright white, it breaks the illusion. You must pass your Theme object directly into the <NavigationContainer theme={MyDarkTheme}> prop in App.js to automatically style the routing components!

11. Practice Exercises

  1. 1. What built-in React Native hook reads the operating system settings to determine if the physical device is in Dark Mode?
  1. 2. If a user sets their OS to Light Mode, but manually toggles Dark Mode inside your app's Settings screen, where must you save that preference so it survives an app restart?

12. MCQs with Answers

Question 1

A developer creates a colors.js file with light and dark palettes. In their component, they write <Text style={{ color: Colors.dark.text }}>Hello</Text>. What is the architectural flaw in this code?

Question 2

When utilizing the useColorScheme hook provided by React Native, what specific action triggers the hook to execute a component re-render?

13. Interview Questions

  • Q: Explain the necessity of combining useColorScheme, Context API, and AsyncStorage to build a production-grade theme system. Why is relying on useColorScheme alone insufficient?
  • Q: Describe the mechanical flaw of utilizing StyleSheet.create() outside of a component function when attempting to apply dynamic theme variables. How do you resolve this?
  • Q: How do you ensure that third-party UI libraries (like React Navigation or UI Kitten) remain perfectly synchronized with your custom application theme state?

14. FAQs

Q: Can I have a "System Default" button in my app? A: Yes! In your Context, instead of storing just 'light' or 'dark', store a third option: 'system'. If the state is 'system', you ignore the hard drive and simply pass the live value from useColorScheme() down to the UI!

15. Summary

In Chapter 28, we delivered the ultimate layer of visual polish to our application. We escaped rigid, hardcoded aesthetics and implemented a fluid Theme System. We captured OS-level visual environments using the useColorScheme hook, established a centralized Color Palette dictionary, and leveraged our knowledge of the Context API to orchestrate global UI updates. By intelligently combining system listeners with AsyncStorage persistence, we empowered users to override defaults and seamlessly switch between light and dark architectures.

16. Next Chapter Recommendation

The code is finished. The app is fast, beautiful, and secure. But it is currently trapped on your laptop. It is time to release it to the world. Proceed to Chapter 29: Publishing and Deployment (EAS).

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·