Skip to main content

2 Easy Ways to Add Dark Mode in a React Native Application

2 Easy Ways to Add Dark Mode in a React Native Application

Learn how to leverage Appearance and Navigation to implement the dark theme in just a few steps.

Introducing themes is a great way to customize the style and appearance of screens in mobile apps. As developers, you may remember the daunting era before React Native when theme integrations used to be tedious, complicated tasks.

But now, implementing a dark theme and switching between the dark and the light modes has become easier than ever, thanks to the modern theme implementation capabilities of React Native. Let’s explore the various ways in which React Native helps developers implement the dark theme.

Different Ways to Implement a Dark Mode

🔥 An application’s theme can be changed in two ways:

  1. By manually changing the theme in the application
  2. By changing the system theme of the device

Let’s learn how to work around both scenarios and make an application compatible with dark mode.

Of course, there are several ways to implement dark mode in a react native app. But I’ll save you some time and share with you the two most efficient ways that have worked very well for our team.

1. Using Appearance

As a core React Native module, Appearance is used for making theme changes on an underlying device. Here’s how to implement the dark mode using Appearance:

Begin by importing the useColorScheme() hook from react-native.

import { useColorScheme } from 'react-native';

Now, use the useColorScheme() hook to identify the theme of your default system. Here, as you will see, the value will change to‘dark’ if the default theme is dark and ‘light’ if the default theme is light.


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

const App = () => {
  const theme = useColorScheme();
  const isDarkTheme = theme === 'dark';
  
  return (
      <View
          style={[
            {
              flex: 1,
              justifyContent: 'center',
              alignItem: 'center',
            },
            isDarkTheme
              ? { backgroundColor: 'black' }
              : { backgroundColor: 'white' },
          ]}>
          <Text style={[isDarkTheme ? { color: 'white' } : { color: 'black' }]}>
            This is demo of default dark/light theme using appearance.{' '}
          </Text>
     </View>
  );
}

export default App;

Using the appearance, you can find out whether the existing theme is dark or light and then, adjust the style accordingly.

2. Using Navigation

Initially, set up react navigation for your project.

After setting up the navigation in your route file, write the following code:

import {
  DarkTheme,
  DefaultTheme,
  NavigationContainer,
} from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import React from 'react';
import { useColorScheme } from 'react-native';
import { ROUTES } from '../constant';
import { HomeScreen } from '../modules';

const RootStack = createStackNavigator();

const AppContainer = () => {
  const theme = useColorScheme();

  return (
    <>
      <NavigationContainer theme={theme === 'dark' ? DarkTheme : DefaultTheme}>
        <RootStack.Navigator>
          <RootStack.Screen name={ROUTES.Home} component={HomeScreen} />
          /** Add screens here....*/
        </RootStack.Navigator>
      </NavigationContainer>
    </>
  );
};

export default AppContainer;

Once your route file has been done, import DarkTheme and DefaultTheme from react-navigation/native, check whether the scheme is dark or light, and set DarkTheme and DefaultTheme accordingly.

It will reflect default colors, and you need to get those colors from useTheme() imported from react-navigation/native.

In your jsx file, enter the following code:


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

const HomeScreen = () => {
  const colors = useTheme().colors;

  return (
    <View
      style={{
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: colors.card,
      }}>
      <Text style={{ color: colors.text }}>
        This is demo of default dark/light theme using navigation.
      </Text>
    </View>
  );
};

export default HomeScreen;

When to use Appearance to implement dark mode

If the app contains a toggling feature for changing the theme, the best way to handle it is by storing value in local storage at your convenience. I am using async storage for storing theme values in local storage.

First, you need to create a colors.js file as per your preference like this,

const commonColor = {
  commonWhite: '#FFFFFF',
  commonBlack: '#000000',
  activeColor: '#DE5E69',
  deactiveColor: '#DE5E6950',
  boxActiveColor: '#DE5E6940',
};

const light = {
  themeColor: '#FFFFFF',
  white: '#000000',
  sky: '#DE5E69',
  gray: 'gray',
  ...commonColor,
};

const dark = {
  themeColor: '#000000',
  white: '#FFFFFF',
  sky: '#831a23',
  gray: 'white',
  ...commonColor,
};

export default { light, dark };

After creating the colors.js file, set up async-storage 💾 in your project and create methods for receiving and saving data from storage.

Note: You can use async storage or redux to store data locally.

Then, create an async storage function in the storage.js file, as shown below:

import AsyncStorage from '@react-native-async-storage/async-storage';

export const saveString = async (key, value) => {
  try {
    await AsyncStorage.setItem(key, value);
    return true;
  } catch (error) {
    return false;
  }
};

export const save = async (key, value) =>
  saveString(key, JSON.stringify(value));

export const get = async key => {
  try {
    const itemString = await AsyncStorage.getItem(key);
    if (itemString) {
      return JSON.parse(itemString);
    } else {
      return null;
    }
  } catch (error) {
    return null;
  }
};

export default {
  saveString,
  save,
  get,
};

Once your function is ready, write code for it in your app.js file to handle the initial installation of the app. Do this when you need to save your default system theme appearance in the local storage.

Note: Here, I have used a real-time scenario to handle the theme. If your app is being installed for the first time on the device, you need to set a theme as the default theme of the device. Later, if you change the theme in the app, it will change accordingly, such as to “Light Mode 🌝 ,” “Dark Mode 🌚 ,” or “System Default.”

Insert the following code into the App.js file:

 const appearance = useColorScheme();
  const setAppTheme = useCallback(async () => {
    const IS_FIRST = await get('IS_FIRST');
    if (IS_FIRST === null) {
      save('Theme', appearance);
      save('IsDefault', true);
      save('IS_FIRST', true);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    setAppTheme();
  }, [setAppTheme]);

Once this is done, design your screen and manage the themes based on your logic. I have implemented my own logic to manage themes using async storage. You can see it in the code below ⬇.


import RadioButtonRN from 'radio-buttons-react-native';
import React, { useCallback, useEffect, useState } from 'react';
import {
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  useColorScheme,
  View,
} from 'react-native';
import { Colors } from '../../theme';
import { get, save } from '../Storage';

const HomeScreen = () => {
  const [themeValue, setThemeValue] = useState('');
  const [initialValue, setInitialValue] = useState(0);
  const themes = useColorScheme();
  const data = [
    {
      label: 'Light Mode',
      value: 'light',
    },
    {
      label: 'Dark Mode',
      value: 'dark',
    },
    {
      label: 'System Default',
      value: 'default',
    },
  ];

  const themeOperations = theme => {
    switch (theme) {
      case 'dark':
        setTheme(theme, false);
        setInitialValue(2);
        return;
      case 'light':
        setTheme(theme, false);
        setInitialValue(1);
        return;
      case 'default':
        setTheme(themes, true);
        setInitialValue(3);
        return;
    }
  };

  const getAppTheme = useCallback(async () => {
    const theme = await get('Theme');
    const isDefault = await get('IsDefault');
    isDefault ? themeOperations('default') : themeOperations(theme);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const setTheme = useCallback(async (theme, isDefault) => {
    save('Theme', theme);
    save('IsDefault', isDefault);
    setThemeValue(theme);
  }, []);

  useEffect(() => {
    getAppTheme();
  }, [getAppTheme]);

  const styles = styling(themeValue);

  return (
    <View style={styles.container}>
      <Text style={styles.textStyle}>
        This is demo of default dark/light theme with switch/Buttons using asycn
        storage.
      </Text>
      <TextInput
        style={styles.textInputStyle}
        placeholder="Type here"
        placeholderTextColor={Colors[themeValue]?.gray}
      />
      <TouchableOpacity style={styles.touchableStyle}>
        <Text style={styles.buttonTextStyle}>Button</Text>
      </TouchableOpacity>
      <RadioButtonRN
        data={data}
        selectedBtn={e => themeOperations(e?.value)}
        initial={initialValue}
        activeColor={Colors[themeValue]?.activeColor}
        deactiveColor={Colors[themeValue]?.deactiveColor}
        boxActiveBgColor={Colors[themeValue]?.boxActiveColor}
        boxDeactiveBgColor={Colors[themeValue]?.themeColor}
        textColor={Colors[themeValue]?.white}
      />
    </View>
  );
};

export default HomeScreen;

const styling = theme =>
  StyleSheet.create({
    container: {
      flex: 1,
      justifyContent: 'center',
      backgroundColor: Colors[theme]?.themeColor,
      paddingHorizontal: 20,
    },
    textStyle: {
      color: Colors[theme]?.white,
    },
    textInputStyle: {
      borderColor: Colors[theme]?.gray,
      padding: 10,
      borderWidth: 2,
      borderRadius: 5,
      width: '100%',
      marginTop: 20,
      color: Colors[theme]?.white,
    },
    touchableStyle: {
      backgroundColor: Colors[theme]?.sky,
      padding: 10,
      borderRadius: 6,
      width: '100%',
      height: 57,
      justifyContent: 'center',
      marginTop: 20,
    },
    buttonTextStyle: {
      textAlign: 'center',
      color: Colors[theme]?.commonWhite,
      fontSize: 20,
      fontWeight: '500',
    },
  });

Here, I have managed dark mode logic using switch cases. And for the UI to change the theme, I have used radio buttons. You can see the output by changing the theme in the application.

For the complete source code, check out the repository on GitHub. 

When to use Navigation to implement dark mode

If the app contains the default theme of the device on which it is running, we need to write it as we discussed above in the navigation example.

First, you need to change the colors object to dark and light in the colors.js file.


const commonColor = {
  colors: {
    commonWhite: '#FFFFFF',
    commonBlack: '#000000',
  },
};

const light = {
  colors: {
    themeColor: '#FFFFFF',
    white: '#000000',
    sky: '#DE5E69',
    gray: 'gray',
    ...commonColor.colors,
  },
};

const dark = {
  colors: {
    themeColor: '#000000',
    white: '#FFFFFF',
    sky: '#831a23',
    gray: 'white',
    ...commonColor.colors,
  },
};

export default { light, dark };

Once the colors object is set, import the Colors in the AppNavigation.js file and write code as follows:

import Colors from '../theme';<NavigationContainer theme={theme === 'dark' ? Colors.dark : Colors.light}>
//....add navigation screen
</NavigationContainer>

The code will check whether the system theme is ‘dark’ or ‘light’, and it will set the colors object accordingly.

Check out the whole AppNavigation.js code below:


import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import React from 'react';
import { useColorScheme } from 'react-native';
import { ROUTES } from '../constant';
import { HomeScreen } from '../modules';
import { Colors } from '../theme';

const RootStack = createStackNavigator();

const AppContainer = () => {
  const theme = useColorScheme();

  return (
      <NavigationContainer
        theme={theme === 'dark' ? Colors.dark : Colors.light}>
        <RootStack.Navigator screenOptions={{ headerShown: false }}>
          <RootStack.Screen name={ROUTES.Home} component={HomeScreen} />
        </RootStack.Navigator>
      </NavigationContainer>
  );
};

export default AppContainer;

After setting up the theme colors object in navigation, you will be able to use that object in your styles.

Now you can design according to your preferences, apply theme-based colors, and look at the output.

In your jsx file, enter the following code:


import { useTheme } from '@react-navigation/native';
import React from 'react';
import { Text, TextInput, TouchableOpacity, View } from 'react-native';

const HomeScreen = () => {
  const colors = useTheme().colors;

  return (
    <View
      style={{
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: colors.themeColor,
        padding: 10,
      }}>
      <Text style={{ color: colors.white }}>
        This is demo of default dark/light theme using navigation.
      </Text>
      <TextInput
        style={{
          borderColor: colors.gray,
          padding: 10,
          borderWidth: 2,
          borderRadius: 5,
          width: '100%',
          marginTop: 20,
          color: colors.white,
        }}
        placeholder="Type here"
      />
      <TouchableOpacity
        style={{
          backgroundColor: colors.sky,
          padding: 10,
          borderRadius: 6,
          width: '100%',
          height: 57,
          justifyContent: 'center',
          marginTop: 20,
        }}>
        <Text
          style={{
            textAlign: 'center',
            color: colors.commonWhite,
            fontSize: 20,
            fontWeight: '500',
          }}>
          Button
        </Text>
      </TouchableOpacity>
    </View>
  );
};

export default HomeScreen;

Here, you can see the output by changing the system theme of the device.

 For the complete source code, check out the repository on GitHub. 

Closing thoughts

The implementation of themes has become a common practice in modern mobile application development processes. Particularly in the case of React Native apps, changing themes between dark and light has never been easier. The easiest approach to implementing a dark mode is through Navigation, but if an app has a toggling feature, then using Appearance is the best way to go about it.

Comments

Popular Posts

What are the Alternatives of device UDID in iOS? - iOS7 / iOS 6 / iOS 5 – Get Device Unique Identifier UDID

Get Device Unique Identifier UDID Following code will help you to get the unique-device-identifier known as UDID. No matter what iOS user is using, you can get the UDID of the current iOS device by following code. - ( NSString *)UDID { NSString *uuidString = nil ; // get os version NSUInteger currentOSVersion = [[[[[UIDevice currentDevice ] systemVersion ] componentsSeparatedByString: @" . " ] objectAtIndex: 0 ] integerValue ]; if (currentOSVersion <= 5 ) { if ([[ NSUserDefaults standardUserDefaults ] valueForKey: @" udid " ]) { uuidString = [[ NSUserDefaults standardDefaults ] valueForKey: @" udid " ]; } else { CFUUIDRef uuidRef = CFUUIDCreate ( kCFAllocatorDefault ); uuidString = ( NSString *) CFBridgingRelease ( CFUUIDCreateString ( NULL ,uuidRef)); CFRelease (uuidRef); [[ NSUserDefaults standardUserDefaults ] setObject: uuidString ForKey: @" udid " ]; [[ NSUserDefaults standardUserDefaults ] synchro...

16 AWS Gotchas

16 AWS Gotchas In January I launched the MVP for my own startup,  Proximistyle , which helps you find what you’re looking for nearby. On advice from friends and industry contacts I chose AWS as my cloud provider. Having never had to set up my own cloud infrastructure before, the learning curve to get from no experience to a stable VPC system I was happy with was significantly steeper than expected, and had its fair share of surprises. #1 Take advantage of the free resources offered AWS offers a free tier for new accounts. If you have recently bought a domain and set up a company you qualify for the free tier for a year. Additionally, if you are a bootstrapped startup you can apply for  the Startup Builders package  and get $1000 in AWS credits. After doing the above, you’re now ready to get started with setting up the AWS infrastructure for your startup. #2 Set up billing budgets and alerting The very first thing you should do after setting up billing, is enabling a budge...

Ultimate Folder Structure For Your React Native Project

  Ultimate Folder Structure For Your React Native Project React native project structure React Native is a flexible framework, giving developers the freedom to choose their code structure. However, this can be a double-edged sword for beginners. Though it offers ease of coding, it can soon become challenging to manage as your project expands. Thus, a structured folder system can be beneficial in many ways like better organization, simplified module management, adhering to coding practices, and giving a professional touch to your project. This write-up discusses a version of a folder arrangement that I employ in my React Native projects. This structure is based on best practices and can be modified to suit the specific needs of your project. Before we get into the project structure let’s give credit to @sanjay who has the original idea of the structure but I modify his version of the code, to make it better. Base library axios  — For network calling. react-navigation ...