DEV Community

Chris Finn
Chris Finn

Posted on • Originally published at thefinnternet.com on

React-Native Dark Mode

Introduction

In this post we will be implementing light and dark mode in our React-Native app using styled-components, context, and react-native-appearance. By the end of the post our app will default to the OS theme on start, update on OS theme change, and toggle light and dark based off of the switch. If a picture says a thousand words than a GIF says like a million or something so just look at this GIF of what we will be making.

Project Demo

Initialize Project

npx react-native init lightSwitch --template react-native-template-typescript cd lightSwitch 
git init git add -A git commit -m "react-native init" 

Add Styled-Components

yarn add styled-components yarn add --dev @types/styled-components 

Theming

Handle System Mode

Currently React-Native doesn’t have an API for checking if the device is set to dark mode so we need this library.

yarn add react-native-appearance 

iOS

cd ios/ pod install cd .. 

Android

Add the uiMode flag

// android/app/src/main/AndroidManifest.xml <activity ... android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"> ... > 

Implement the onConfigurationChanged method

// android/app/src/main/java/com/<PROJECT_NAME>/MainActivity.java import android.content.Intent; // <--- import import android.content.res.Configuration; // <--- import public class MainActivity extends ReactActivity { ...... // copy these lines @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); Intent intent = new Intent("onConfigurationChanged"); intent.putExtra("newConfig", newConfig); sendBroadcast(intent); } ...... } 

Define Types

// types.ts export type ThemeMode = 'light' | 'dark'; export interface ThemeContext { mode: ThemeMode; setMode(mode: ThemeMode): void; } export interface Theme { theme: { background: string; border: string; backgroundAlt: string; borderAlt: string; text: string; }; } 

Add Themes

Light

// themes/light.ts import {Theme} from '../types'; const light: Theme = { theme: { background: '#ededed', border: '#bdbdbd', backgroundAlt: '#eaeaeb', borderAlt: '#bdbdbd', text: '#171717', }, }; export default light; 

Dark

// themes/dark.ts import {Theme} from '../types'; const dark: Theme = { theme: { background: '#2E3440', border: '#575c66', backgroundAlt: '#575c66', borderAlt: '#2E3440', text: '#ECEFF4', }, }; export default dark; 

Create Context

The ManageThemeContext is where all the magic happens. Here we create a new context, get the current OS mode, pass the theme into the styled-component’s ThemeProvider, register for OS mode changes, and set the status bar color appropriately.

// contexts/ManageThemeContext.tsx import React, {createContext, useState, FC, useEffect} from 'react'; import {ThemeMode, ThemeContext} from '../types'; import {StatusBar} from 'react-native'; import {ThemeProvider} from 'styled-components/native'; // @ts-ignore import {Appearance, AppearanceProvider} from 'react-native-appearance'; import lightTheme from '../themes/light'; import darkTheme from '../themes/dark'; // Get OS default mode or default to 'light' const defaultMode = Appearance.getColorScheme() || 'light'; // Create ManageThemeContext which will hold the current mode and a function to change it const ManageThemeContext = createContext<ThemeContext>({ mode: defaultMode, setMode: mode => console.log(mode), }); // Export a helper function to easily use the Context export const useTheme = () => React.useContext(ManageThemeContext); // Create the Provider const ManageThemeProvider: FC = ({children}) => { const [themeState, setThemeState] = useState(defaultMode); const setMode = (mode: ThemeMode) => { setThemeState(mode); }; // Subscribe to OS mode changes useEffect(() => { const subscription = Appearance.addChangeListener( ({colorScheme}: {colorScheme: ThemeMode}) => { setThemeState(colorScheme); }, ); return () => subscription.remove(); }, []); // Return a component which wraps its children in a styled-component ThemeProvider, // sets the status bar color, and injects the current mode and a function to change it return ( <ManageThemeContext.Provider value={{mode: themeState as ThemeMode, setMode}}> <ThemeProvider theme={themeState === 'dark' ? darkTheme.theme : lightTheme.theme}> <> <StatusBar barStyle={themeState === 'dark' ? 'light-content' : 'dark-content'} />  {children} </>  </ThemeProvider>  </ManageThemeContext.Provider>  ); }; // This wrapper is needed to add the ability to subscribe to OS mode changes const ManageThemeProviderWrapper: FC = ({children}) => ( <AppearanceProvider> <ManageThemeProvider>{children}</ManageThemeProvider>  </AppearanceProvider> ); export default ManageThemeProviderWrapper; 

Example Usage

This is the example code behind the GIF at the top of this post.

import React from 'react'; import {Theme} from './types'; import styled from 'styled-components/native'; import ThemeManager, { useTheme } from './contexts/ManageThemeContext'; import { Switch } from 'react-native'; const Home = () => { // Helper function => useContext(ManageThemeContext) const theme = useTheme(); return ( <Container> <Switch value={theme.mode === 'dark'} onValueChange={value => theme.setMode(value ? 'dark' : 'light')} />  </Container>  ); }; // Get the background color from the theme object const Container = styled.View<Theme>` flex: 1; justify-content: center; align-items: center; background: ${props => props.theme.background}; `; // Wrap Home in the ThemeManager so it can access the current theme and // the function to update it const App = () => ( <ThemeManager> <Home /> </ThemeManager> ); export default App; 

Conclusion

Adding a light and dark mode to your app is pretty simple and adds a cool dynamic. It is also the hot, new thing to do so get to it!

Checkout This Project’s Code On Github

Top comments (0)