loading...
Cover image for iOS dark mode with React Native

iOS dark mode with React Native

cristianrita profile image Cristian Riță ・4 min read

Introduction

iOS 13 introduced Dark Mode, a feature that lets users choose between a system-wide light or dark theme. At that time many people saw this more as a gimmick. Soon after, Whatsapp, Facebook Messenger, Youtube, or Gmail came up with both light and dark versions of their iOS apps.

Facebook Messenger Dark vs Light version
Image from The Verge

Start from here

Before designing your app, make sure you read Apple Human Interface Guidelines. I will not go into design details but here is a great resource.

Key takeaways:

  • iOS provides dynamic system colors that automatically adapt to light or dark modes.
  • your app should comply with the appearance mode people choose in Settings.

Let's build an app

I will create a new application using React Native CLI. You can follow along or check the final version on Github.

npx react-native init DarkModeExample
Enter fullscreen mode Exit fullscreen mode

Make sure you remove generated code from App.js and replace it with the following

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

const App = () => (
  <View style={styles.container}>
    <Text>Hello World</Text>
  </View>
);

const styles = StyleSheet.create({
  container: {
    justifyContent: 'center',
    alignItems: 'center',
    flex: 1,
  },
});

export default App;
Enter fullscreen mode Exit fullscreen mode

Now we are ready to customize the app.

1. Launch Screen

Let's start the customization with the Launch Screen. This is the splash screen which appears for a few moments when the application is launched.

Open the project in Xcode.
Xcode Project

Go to LaunchSreen.storyboard and make sure you change the background color of the View to SystemBackground. SystemBackground is pure white for the light theme and pure black for the dark one. I also changed the color of the "DarkModeExample" text to System Orange Color.

To see the result, on your simulator, go to Settings->Developer->Appearance, switch between dark and light appearance, and open the app. Depending on what you selected, the LaunchScreen should change accordingly.

Alt Text

2. Add a login screen

For demonstration purposes, we will design a login screen.
Many UI kits and libraries offered theming capabilities even before Dark Mode landed on iOS. Most of them rely on React Context to provide this kind of functionality.

React Native 0.63 introduced PlatformColor. PlatformColor lets you access native colors on the target platform by supplying the native color’s corresponding string value.

backgroundColor: PlatformColor('systemBackground')
Enter fullscreen mode Exit fullscreen mode

systemBackground is a native iOS color. More than that it is dynamic which means its value is #fff for the light theme and #000 for the dark one. The color will automatically change when the theme is changed by the user in Settings.

Now let's update the App.js file:

import React from 'react';
import {
  KeyboardAvoidingView,
  Platform,
  PlatformColor,
  Pressable,
  StyleSheet,
  Text,
  TextInput,
  View,
} from 'react-native';

const App = () => {
  return (
    <View style={styles.container}>
      <KeyboardAvoidingView
        style={styles.contentContainer}
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
        <View style={styles.form}>
          <TextInput
            paddingLeft={10}
            autoCapitalize="none"
            autoCorrect={false}
            keyboardType="email-address"
            placeholder="Email"
            placeholderTextColor={Platform.select({
              ios: PlatformColor('secondaryLabel'),
              android: 'white',
            })}
            style={styles.input}
          />

          <TextInput
            paddingLeft={10}
            secureTextEntry
            autoCapitalize="none"
            autoCorrect={false}
            placeholder="Password"
            placeholderTextColor={Platform.select({
              ios: PlatformColor('secondaryLabel'),
            })}
            style={styles.input}
          />

          <View>
            <Pressable style={styles.loginButton}>
              <Text style={styles.label}>Login</Text>
            </Pressable>
          </View>
        </View>
      </KeyboardAvoidingView>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    ...Platform.select({
      ios: {backgroundColor: PlatformColor('systemBackground')},
      default: {
        backgroundColor: '#000000ff',
      },
    }),
  },
  contentContainer: {
    flex: 1,
    maxHeight: '90%',
    flexDirection: 'column',
    justifyContent: 'space-evenly',
    alignItems: 'center',
    ...Platform.select({
      ios: {backgroundColor: PlatformColor('systemBackground')},
      default: {
        backgroundColor: '#000000ff',
      },
    }),
  },
  form: {
    width: '90%',
    justifyContent: 'space-between',
    borderRadius: 5,
  },
  input: {
    height: 40,
    marginTop: 10,
    fontWeight: '500',
    borderWidth: 0.3,
    borderRadius: 5,
    ...Platform.select({
      ios: {
        color: PlatformColor('labelColor'),
        backgroundColor: PlatformColor('tertiarySystemBackground'),
        borderColor: PlatformColor('separator'),
      },
      default: {
        backgroundColor: '#1c1c1eff',
        borderColor: '#54545899',
      },
    }),
  },
  loginButton: {
    width: '100%',
    justifyContent: 'center',
    borderRadius: 5,
    height: 40,
    marginTop: 40,
    ...Platform.select({
      ios: {backgroundColor: PlatformColor('systemBlue')},
      android: {backgroundColor: '#0a84ffff'},
      default: {
        backgroundColor: '#0a84ffff',
      },
    }),
  },
  label: {
    fontWeight: '600',
    color: 'white',
    width: '100%',
    fontSize: 20,
    textAlign: 'center',
  },
});

export default App;
Enter fullscreen mode Exit fullscreen mode

Note: Chances are this will not be responsive and won't look good on every screen size

Switch again between light and dark themes and see how colors are automatically updated.

Alt Text

As you can see I used PlatformColor to get different iOS native colors. For a full list check this.

PlatformColor('systemBlue');
Enter fullscreen mode Exit fullscreen mode

These colors are only available on iOS 13+ so for earlier versions of iOS or for the Android platform we should provide alternative values. This can be accomplished using PlatformSelect.

...Platform.select({
      ios: {backgroundColor: PlatformColor('systemBlue')},
      android: {backgroundColor: '#0a84ffff'},
      default: {
        backgroundColor: '#0a84ffff',
      },
    })
Enter fullscreen mode Exit fullscreen mode

3. Add dynamic logo

The only missing part of our login screen is the logo which is usually an image. There is a pretty high chance our logo will not look good on both black and white backgrounds. To fix this we will need a light and a dark version for the logo.

First, we will create the following folder structure

DarkModeExample
│   
│   
│
└───src
    └───assets
    │    │   logo_dark.png
    │    │   logo_light.png
    │    │ 
    └─── components
          │  LogoComponent.js
          │
Enter fullscreen mode Exit fullscreen mode

You can get the images from my Github repository.

Now let's implement the LogoComponent.

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

const LogoComponent = (props) => {
  const colorScheme = useColorScheme();

  return colorScheme === 'dark' ? (
    <Image source={require('./../assets/logo_dark.png')} style={{...props}} />
  ) : (
    <Image source={require('./../assets/logo_light.png')} style={{...props}} />
  );
};

export default LogoComponent;
Enter fullscreen mode Exit fullscreen mode

As you can see, React Native provides useColorScheme() hook which returns the active color scheme as a string. Based on that we return the proper version of the logo.

All we need to do is to import the LogoComponent in our App.js and render it just above the form.

Alt Text

Conclusion

You made it here! Thank you! At this point, you should be able to implement Dark Mode into your React Native app 🚀

Discussion

pic
Editor guide