Benteh Jallow
7min read
How to setup Dark Mode in React Native using Expo
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!