How to setup Dark Mode in React Native using Expo

post image

Benteh Jallow

8min read

post image

If you don’t want to have one of those apps that would blind users in the middle of the night when in use, then this article would show you a step-by-step guide on how to set up dark mode in React Native App using Expo.

In this article, we are going to break down the process into two segments. First, we are going to activate the light and dark mode theme based on the preferred color scheme of the user and also have a switch button to manually toggle light and dark mode on our app.

Let’s get started!

Note: If you don’t want to go through the whole rigamarole process of installing and setting up a new app, then feel free to skip this part.

Paste this code below in your terminal if you don’t have the EXPO-CLI installed already

npm install -global expo-cli

Initializing a new project

Create a new project name dark-mode-app or any name that you want. Select the “blank” template when prompted

expo init dark-mode-app

Navigate to the project directory

cd dark-mode-app

Starting the development server

expo start

Voila. Our app is up and running.

Open the project in your preferred code editor and let’s welcome dark mode into the land of light.

export default function App() {
 return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text
      <StatusBar style="auto" />
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    backgroundColor: '#fff',
    justifyContent: 'center',},
   });

We need to install React Navigation and two other libraries react-native-screens and react-native-safe-area-context

You can install react-navigation with either yarn or npm

// Install using Yarn
yarn add @react-navigation/native

// Install using NPM
npm install @react-navigation/native

Installing dependencies into an Expo managed project

expo install react-native-screens react-native-safe-area-context

Now, all we need to do is to wrap our entire app in NavigationContainer. Usually, you’d do this in your entry file, such as index.js or App.js. In our case, the entry file is App.js. If we wrap the NavigationContainer to our entire app, our code should look something like this.

// App.js

import * as React from 'react';

import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';

export default function App() {
  return (
    <NavigationContainer>
      <View style={styles.container}>
        <Text>Open up App.js to start working on your app!</Text
        <StatusBar style="auto" />
      </View>
    </NavigationContainer>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    backgroundColor: '#fff',
    justifyContent: 'center',
  },
});

Let’s create a custom component called CustomComponent.js and move our content in there then we can call the component inside the App.js file

// CustomComponent.js

import * as React from 'react';

import { SafeAreaView, StyleSheet, View, Text } from 'react-native';

export default function CustomComponent() {
  return (
    <SafeAreaView style={styles.safeArea}>
      <View>
        <Text style={styles.text}>
          We are working with Dark Mode
        </Text>
      </View>
    </SafeAreaView>
  )
};
const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 16,
  },
});

Now that we have moved the content in the CustomComponent.js file, our code in the App.js the file should look something like this:

// App.js

import * as React from 'react';

import { StatusBar } from 'expo-status-bar';
import { NavigationContainer } from '@react-navigation/native';
import CustomComponent from './CustomComponent';

export default function App() {
  return (
    <NavigationContainer>
      <CustomComponent />
      <StatusBar style="auto" />
    </NavigationContainer>
  );
};

Great! Let’s open the app.json file and configure the supported appearance styles with the “userInterfaceStyle” key.

The available options are: automatic (follow system appearance settings and notify about any change user makes), light (restrict the app to support light theme only), and dark (restrict the app to support dark theme only). If this key is absent, the app will default to the light style.

Add “userInterfaceStyle”: “automatic” in your app.json file

// app.json

{
  "expo": {
    "name": "dark-mode-app",
    "slug": "dark-mode-app",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "automatic",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
  "updates": {
    "fallbackToCacheTimeout": 0
  },
  "assetBundlePatterns": [
    "**/*"
  ],
  "ios": {
    "supportsTablet": true
  },
  "android": {
    "adaptiveIcon": {
      "foregroundImage": "./assets/adaptive-icon.png",
      "backgroundColor": "#FFFFFF"
    }
  },
  "web": {
    "favicon": "./assets/favicon.png"
   }
  }
}

Themes

Themes allow us to change the colors of various components provided by React Navigation. We can use themes to:

  • Customize the colors that match our brand
  • Provide light and dark themes based on the time of the day or user preference

We are going to create two new files called appLightTheme.js and appDarkTheme.js

These two files are going to hold the themes of our app and use them to toggle the theme based on a light or dark. We are going to import DefaultTheme & DarkTheme from react-navigation.

appLightTheme.js

import { DefaultTheme } from '@react-navigation/native';

const AppLightTheme = {
  ...DefaultTheme,
  dark: true,
  colors: {
    ...DefaultTheme.colors,
    text: '#616161',
    card: '#f9f9f9',
    border: '#9F9F9F',
    primary: '#333333',
    background: '#ffffff',
  }
}

export default AppLightTheme;

appDarkTheme.js

import { DarkTheme } from '@react-navigation/native';

const AppDarkTheme = {
  ...DarkTheme,
  dark: false,
  colors: {
    ...DarkTheme.colors,
    text: '#dadada',
    card: '#191919',
    border: '#444859',
    primary: '#f9f9f9',
    background: '#121212',
  }
}

export default AppDarkTheme;

Detecting the color scheme

In order to detect the color scheme in our application, we can use Appearance and/or useColorScheme from react-native

Let’s use the useColorScheme hook from react-native to detect the color scheme in our app and dynamically change the theme based on the active color scheme.

To pass a custom theme, we can pass the theme prop to the NavigationContainer

import * as React from 'react';

import { StatusBar } from 'expo-status-bar';
import { useColorScheme } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';

import AppDarkTheme from './darkTheme';
import AppLightTheme from './lightTheme';
import CustomComponent from './CustomComponent';

export default function App() {
  const colorScheme = useColorScheme();

  return (
    <NavigationContainer
      theme={colorScheme === 'light' ? AppLightTheme : AppDarkTheme}
    >
      <CustomComponent />
      <StatusBar style="auto" />
    </NavigationContainer>
  );
};

Using the current theme in your own components

To gain access to the appLightTheme and appDarkTheme themes in any component that is rendered inside the navigation container, you can use the useTheme hook. It returns the theme object:

// CustomComponent

import * as React from 'react';

import { useTheme } from '@react-navigation/native';
import { SafeAreaView, StyleSheet, View, Text } from 'react-native';

export default function CustomComponent() {
  const { colors } = useTheme();
return (
    <SafeAreaView
      style={[
        styles.safeArea,
        { backgroundColor: colors.background }
      ]}
    >
      <View>
        <Text style={{ fontSize: 16, color: colors.text }}>
          We are working with Dark Mode!
        </Text>
      </View>
    </SafeAreaView>
  )
}
const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  }
});

Now let’s toggle light and dark using the shortcut command + shift + a and watch the magic happen!

Congratulations on completing the first step!

Now that we are able to detect the color scheme of the user’s device and change the theme accordingly, the final part is to add a switch button and manually toggle light and dark mode in our app.

To do that, let’s create another new file called ThemeContext.js in our project directory. We are going to leverage the react-context-hook.

React Context is a method to pass props from parent to child component(s), without actually passing them manually at each level of the component tree.

ThemeContext.js

// ThemeContext.js

import React from 'react';

import { Appearance } from 'react-native';

const defaultMode = Appearance.getColorScheme();

export default ThemeContext = React.createContext({
  theme: defaultMode
});

Let’s import the ThemeContext we just created in our App.js file and wrap it around our entire application. Next thing, we would create a theme and setTheme hook as a state that we can pass down to the ThemeContext.Provider so that we can be able to access that state anywhere in our application.

Example code below

import { StatusBar } from 'expo-status-bar';
import { useColorScheme } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';

import AppDarkTheme from './darkTheme';
import AppLightTheme from './lightTheme';
import CustomComponent from './CustomComponent';

export default function App() {
  const colorScheme = useColorScheme();

  const [theme, setTheme] = React.useState(colorScheme);
  const themeData = { theme, setTheme };

return (
    <ThemeContext.Provider value={themeData}>
      <NavigationContainer
        theme={colorScheme === 'light'
        ? AppLightTheme
        : AppDarkTheme
       }
      >
        <CustomComponent />
        <StatusBar style="auto" />
      </NavigationContainer>
    </ThemeContext.Provider>
  );
};

Lastly, we are going to create our last and final file called SwitchButton.js. As you may already guess, it’s the component for the Switch button.

// SwitchButton.js

import React, { useState, useContext } from 'react';

import { Switch, View, useColorScheme } from 'react-native';

import ThemeContext from './ThemeContext';

const SwitchButton = () => {
  const colorScheme = useColorScheme();

  const { setTheme, theme } = useContext(ThemeContext);
  const [isEnabled, setIsEnabled] = useState(false);

  const toggleSwitch = () => {
    setIsEnabled(prevState => !prevState);
    setTheme(() => setTheme(theme === 'light' ? 'dark' : 'light'));
  };

  return (
    <View style={{ marginTop: 50 }}>
      <Switch
        value={isEnabled}
        onValueChange={toggleSwitch}
        disabled={colorScheme === 'dark'}
      />
    </View>
  );
};

export default SwitchButton;

Job well done!

Let’s lean back and toggle the switch button or hit command + shift + a on our keyboard to change the color scheme and see our beloved light and dark themes taking effect.

I hope this article was helpful. Thank you so much for reading!

redirect arrow
Go Back