How to Add Support for Dark and Light Themes in React Native — React native dark theme example

Lonare
7 min readJun 7, 2021

If you are a React-Native developer or looking to start development of your mobile apps in React Native and you want to make sure that as per your users choice your react native app must adapt the dark or light theme then you are at the right place.

In this article I will explain to you how you can achieve dark theme support in your react native app based on your users settings on their devices.

Dark Mode was introduced in iOS 13. It adds a darker theme to iOS and allows you to do the same for your app. It’s a great addition to give to your users so they can use your app in a darker design.

In this blog post, we are going to see how to add it in your react native app very quickly with React navigation v5 and Expo.

Create the expo app

if you dont have the expo-cli installed run this before:

npm install -g expo-cli

and then :

expo init

Now one more step you need to follow which is updating the expo sdk. This is because the new navigation does not support the older versions of expo SDK like 35 or 36

It requires minimum version 41.0.0

So in order to do that you need to run the following command:

expo update 41

If you do not have yarn then you can easily install it via your terminal just type this command:

npm install -g yarn

You will also need to upgrade your React version to v17

yarn add react@17.0.0 react-dom@17.0.0

Adding navigation

Let’s install React navigation(the latest version v6)

yarn add @react-navigation/native@next

We need to install a few more libraries for this to work.

In the project directory, run

expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context

This will install versions of these libraries that are compatible.

We have React navigation installed in our project, we can start using it by creating a simple stack navigator with 2 screens.

  • Install the stack navigator and masked view
yarn add @react-navigation/stack@next @react-native-masked-view/masked-view

Creating a stack navigator#

createStackNavigator is a function that returns an object containing 2 properties: Screen and Navigator. Both of them are React components used for configuring the navigator. The Navigator should contain Screen elements as its children to define the configuration for routes.

NavigationContainer is a component which manages our navigation tree and contains the navigation state. This component must wrap all navigators structure. Usually, we'd render this component at the root of our app, which is usually the component exported from App.js.

// In App.js in a new projectimport * as React from 'react';
import { View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
function HomeScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
</View>
);
}
const Stack = createStackNavigator();function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;

After you save this code into your project you will see a screen like one below:

Configuring the navigator#

All of the route configuration is specified as props to our navigator. We haven’t passed any props to our navigator, so it just uses the default configuration.

Let’s add a second screen to our stack navigator and configure the Home screen to render first:

function DetailsScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
</View>
);
}
const Stack = createStackNavigator();function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}

Now our stack has two routes, a Home route and a Details route. A route can be specified by using the Screen component. The Screen component accepts a name prop which corresponds to the name of the route we will use to navigate, and a component prop which corresponds to the component it'll render.

Here, the Home route corresponds to the HomeScreen component, and the Details route corresponds to the DetailsScreen component. The initial route for the stack is the Home route. Try changing it to Details and reload the app (React Native's Fast Refresh won't update changes from initialRouteName, as you might expect), notice that you will now see the Details screen. Then change it back to Home and reload once more.

Moving between screens

In the previous section, “Hello React Navigation”, we defined a stack navigator with two routes (Home and Details), but we didn't learn how to let a user navigate from Home to Details (although we did learn how to change the initial route in our code, but forcing our users to clone our repository and change the route in our code in order to see another screen is arguably among the worst user experiences one could imagine).

import * as React from 'react';
import { Button, View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button
title="Go to Details"
onPress={() => navigation.navigate('Details')}
/>
</View>
);
}
// ... other code from the previous section

Notice how we added {navigation} as prop in our HomeScreen funtion. Let's add another piece of code for Detail Screen as below:

function DetailsScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
<Button
title="Go to Home"
onPress={() => navigation.navigate('Home')}
/>
</View>
);
}
}

It will look something like this:

Adding dark mode support

Now we want to provide light and dark themes based on the user preference.

  • Let’s install react-native-appearance to detect the operating system color scheme preferences.
expo install react-native-appearance
or
yarn add react-native-appearance@next
  • Then, in app.json, under the ios key we need to add “userInterfaceStyle”: “automatic”
"ios": {"userInterfaceStyle": "automatic"}
  • Now we can import AppearanceProvider and useColorScheme(return the operating system color scheme)
import { AppearanceProvider, useColorScheme } from "react-native-appearance";
  • React navigation provide by default a Dark theme and a defaultTheme we can import it and use it
import { DefaultTheme, DarkTheme } from “@react-navigation/native”;// Add this inside your components where you want to use the theme
const scheme = useColorScheme();

In our case we will be using it with Navigation Container so use this code below:

theme={ scheme === ‘dark’ ? DarkTheme : DefaultTheme }
Notice how we added the scheme constant and used it to define the theme colour.

When I change the color scheme of my operating system, the theme of my app changes automatically. Cool 😍

But we can notice that the text is not displayed when the theme is dark because this is a black text in a black background. We can adjust the text color by using the current theme color text.

To gain access to the theme in any component that is rendered inside the navigation container: we can use the useTheme hook. It returns the theme object.

The colored text automatically adapts to the current schema.

Custom themes

Instead of using themes provided by React navigation we can create our own themes.

A theme is a JS object containing a list of colors to use. It contains the following properties:

  • dark (boolean): Whether this is a dark theme or a light theme
  • colors (object): Various colors used by react navigation components:
  • primary (string): The primary color of the app used to tint various elements. Usually you’ll want to use your brand color for this.
  • background (string): The color of various backgrounds, such as background color for the screens.
  • card (string): The background color of card-like elements, such as headers, tab bars etc.
  • text (string): The text color of various elements.
  • border (string): The color of borders, e.g. header border, tab bar border etc.
  • notification (string): The color of Tab Navigator badge.

When creating a custom theme, you will need to provide all of these properties.

I am sure you have a question, how can I define my dark theme Constant in a separate file. If you do let me know in the comments section.

--

--

Lonare

Imagination is the key to unlock the world. I am trying to unlock mine.