Dark/Light theme in react-native app

A simple straight-forward approach of effectively implementing dark more and light mode in your react-native app

Chaudhry Talha 🇵🇸
7 min readSep 28, 2023
Photo by Zan on Unsplash

For the demonstration purpose of this article I will first create an example project that will have just two colors i.e. Black and White. Which will be enough for you to make your own and then as a bonus I’ve shared an example for multi-color apps.

  • An example app with only two colors Black and White
  • Extension of part 1 but with more colors other than black and white

In most of the react-native apps I create there is always a file that looks something like the code snippet below that holds all the main colors.

export const colors = {
black: '#000',
white: '#fff',
}

Today we’ll create a type of all the possible colors we’re going to have in the app. For this particular example I only have a black and a white color. I can call it as primary and secondary as depending on light or dark mode there will be a primary dominate color accordingly.

So add the code in your typing file:

export type Colors = {
primary: string;
secondary: string;
};

I have created a file named index.ts inside a …/util/style folder in my project. In this file first add these two const:

const COLORS_LIGHT: Colors = {
secondary: '#212427',
primary: '#F5F5F5',
};

const COLORS_DARK: Colors = {
secondary: '#F5F5F5',
primary: '#212427',
};

Both the colors are opposite of each-other. In dark colors the primary color is the darker one and vice versa.

React-native provides a hook named useColorScheme which returns us values like light, dark depending on which mode is on the user device and we can use these to change colors accordingly.

Let’s create an enum for the values that the hook is going to return first in the same index.ts file:

enum COLOR_SCHEME {
LIGHT = 'light',
DARK = 'dark',
}

Now we need an export a const from index.ts that’ll return us the color scheme depending on what mode is on user’s phone:

export const COLORS = {
[COLOR_SCHEME.LIGHT]: COLORS_LIGHT,
[COLOR_SCHEME.DARK]: COLORS_DARK,
};

Now that we have defined the colors, we’re going to do two things:

  • As I’m using Stack Navigation so I want my Stack Navigator to be dark as well and iOS and Android comes with a default dark and light mode navigation bar as well.
  • Creating a screen and using the COLORS

You can implement stack navigator in your react-native app and then in App.tsx add this:

import React from 'react';
import { useColorScheme } from 'react-native';

import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

import TestScreen from './src/screens/TestScreen';

function App() {

const Stack = createStackNavigator();
const colorScheme = useColorScheme();

return (
<NavigationContainer
theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}
>
<Stack.Navigator>
<Stack.Screen name="TestScreen" component={TestScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}

export default App;

Now I can add as many screen here and each will be wrapped with a theme switch and the hook useColorScheme keeps a track of any change. You can use the same technique if you’re using other types of navigation like TabBar or others.

Create the TestScreen.tsx file and add the following code:

//... Imports

function TestScreen() {

const navigation = useNavigation();
const colorScheme = useColorScheme();
const colors = COLORS[colorScheme ?? 'dark'];

useEffect(() => {
navigation.setOptions({
headerTitleAlign: 'left',
headerTitle: 'Test Screen',
headerStyle: {
backgroundColor: colors['primary'],
shadowOpacity: 0,
shadowOffset: {
height: 0,
width: 0,
},
shadowRadius: 0,
elevation: 0,
},
headerTitleStyle: {
color: colors['secondary'],
},
});
}, [navigation, colorScheme]);

return (
<ScrollView contentContainerStyle={[styles.container, { backgroundColor: colors['primary'] }]} showsVerticalScrollIndicator={false}>
<View style={[styles.logoContainer, { borderColor: colors['secondary'] }]}>
<Text style={[styles.logoNumberText, { color: colors['secondary'] }]}>1</Text>
{/* SVG Icon */}
{/* <InfoCircleSvg /> */}
<Text>ⓘ</Text>
</View>
<Text style={[styles.mainInfoTextBase, { color: colors['secondary'] }]}>Bold Text</Text>
<Text style={{ color: colors['secondary'] }}>Regular Text</Text>
<TouchableOpacity style={styles.linkButton}>
<Text style={[styles.linkButtonText, { color: colors['secondary'] }]}>Link Text</Text>
</TouchableOpacity>
<View style={[styles.divider, { backgroundColor: colors['secondary'] }]} />

</ScrollView>
)

}

//... Stylings: See after description

In the code above for test I have tried adding different components like Text, Shapes etc. The useColorScheme flag returns us light, dark and null based on the current active mode in the phone and it’s return values are as same as what we defined in our COLOR_SCHEME enum above. We have imported the COLORS and then used the value from useColorScheme hook to get the primary and secondary colors of the current active mode.

We’re also over writing the navigation bar by provide it the colors in the same way as other elements i.e. colors[‘secondary’]. I’m doing this to blend the navigation bar in with my screen.

Here is the rest of the code for the TestScreen:

const styles = StyleSheet.create({
container: {
padding: 16,
},
logoContainer: {
height: 100,
width: 100,
flexDirection: 'row',
alignSelf: 'center',
alignItems: 'center',
justifyContent: 'center',
marginVertical: 8,
borderWidth: 3,
borderRadius: 20,
},
logoNumberText: {
fontSize: 32,
lineHeight: 40,
fontWeight: 'bold'
},
mainInfoTextBase: {
fontWeight: 'bold',
fontSize: 16,
marginVertical: 8
},
divider: {
height: 1,
marginVertical: 8
},
linkButton: {
marginVertical: 8
},
linkButtonText: {
textDecorationLine: 'underline',
fontWeight: 'bold',
}
})

export default TestScreen;

This is how the screen looks currently.

Now I’m going to test it for android simulator only. While the app is running and without refreshing open a new terminal window and run the following commands to turn dark mode of the emulator on or off:

adb shell "cmd uimode night yes"
adb shell "cmd uimode night no"

The dark color I choose is not a pure black so you can see the difference in the backgroun. A simple flex: 1 would take care of that but now as user’s phone mode changes you app will change colors accordingly.

EXTRA: Adding dark light mode to SVG icons

In the code above I have a commented SVG Icon <InfoCircleSvg /> and it is what you’re seeing as the info icon in the images above.

Use the link below to generate your own MySvgIconFile.tsx file:

Once you have the SVG icon in the tsx file you can then use the same hook useColorScheme to get the mode and change the color. For example below is the code I have for the InfoCircleSvg.tsx file:

import * as React from "react";
import { useColorScheme } from "react-native";
import Svg, { Path } from "react-native-svg";
//... Imports

const InfoCircleSvg = () => {
const colorScheme = useColorScheme();
const colors = COLORS[colorScheme ?? 'dark'];
return (<Svg
width={30}
height={30}
viewBox="0 0 30 30"
fill="none"
>
<Path
fillRule="evenodd"
clipRule="evenodd"
d="M27.5 15C27.5 21.9035 21.9035 27.5 15 27.5C8.09644 27.5 2.5 21.9035 2.5 15C2.5 8.09644 8.09644 2.5 15 2.5C21.9035 2.5 27.5 8.09644 27.5 15ZM15 22.1875C15.5177 22.1875 15.9375 21.7677 15.9375 21.25V13.75C15.9375 13.2323 15.5177 12.8125 15 12.8125C14.4823 12.8125 14.0625 13.2323 14.0625 13.75V21.25C14.0625 21.7677 14.4823 22.1875 15 22.1875ZM15 8.75C15.6904 8.75 16.25 9.30965 16.25 10C16.25 10.6903 15.6904 11.25 15 11.25C14.3096 11.25 13.75 10.6903 13.75 10C13.75 9.30965 14.3096 8.75 15 8.75Z"
fill={colors['secondary']}
/>
</Svg>)
};
export default InfoCircleSvg;

The file which was generated by the svgviewer it was easy to color the paths. This icon is a simple one, for a colored one you can change the colors on every layer of it. You can see in the screenshot how well the SVG blends with the mode.

BONUS: CONFIGURING THE APP WITH MULTI-COLORS

Lets have a look at an example where you have multiple colors in the app and not just black and white. Naming matters here as people usually names things like background_light_primary, background_dark_primary, background_dark_secondary, background_dark_primary, button_dark_background etc.

For an example I’ve taken this image from the internet, when I see this I can think of the following colors share after the picture:

I might be making the icons with SVG icons and Views and with a bit different colors to cover most of the different colors. Open your typings file and edit the Colors type with the color you see in the above UI:

export type Colors = {
primary_background: string;
primary_text: string;
secondary_text: string;
darker_text: string;
right_arrow_pointer: string;
switch_on_bg: string;
timer_icon_bg: string;
schedule_icon_bg: string;
lock_children_icon_bg: string;
};

You can name the colors anything but I gave them the name based on what I want changing when the mode change.

You already have index.ts in …/util/style folder. Open that and add/update the above colors in the COLORS_LIGHT and COLORS_DARK constants accordingly. You will have to define both color code i.e. when the dark more is on which hex color will primary_background have will be determined in COLORS_LIGHT and COLORS_DARK constants.

So this is how you can define the colors accordingly and as the user change theme on their phone or the theme changes automatically your app will adapt.

--

--