Implementing Light and Dark Theme in React Native: Complete Guide
On mobile phones and tablets, the system theme can be set to dark or light. In mobile applications, the system theme affects the color unless specified. For example, if text is styled in a React Native application and no color is specified, it will appear white if the system theme is dark, and black if the system theme is light. Failing to make these adjustments can lead to design issues in the application. Today, I'll explain how to easily achieve this when developing a React Native mobile app using simple code. Because the app doesn't have a pre-selected theme when it first launches, it will inherit the system theme. If it's selected in our application, we'll explain how to take action accordingly.
First, we'll go over colors when starting the application. Here, we'll create a theme.js file for the colors. Instead of directly typing the colors, we need to call the colors in this file. Here, we'll name them according to their tones. Conversely, the settings for the dark theme are made. This is done for light theme, and colors.primary_50 (#fdf4e6) will automatically be used here. If the theme is light, it will automatically use the color code #2a1501. This will differentiate between light and dark themes. We've also defined the colors to be used regardless of light or dark theme in the commonColors section, and included these in the light and dark objects.
theme.js
const commonColors = {
opacity_primary: `rgba(56, 58, 66, 0.44)`,
black: `#000000`,
white: `#ffffff`,
transparent: `transparent`,
secondary: `#333333`,
lightGray: `#979797`,
}
export const COLORS = {
light: {
primary_50: `#fdf4e6`,
primary_100: `#fbe8cd`,
primary_200: `#f5cf9c`,
primary_300: `#ecb566`,
primary_400: `#d8902d`,
primary_500: `#a16207`,
primary_600: `#8b5106`,
primary_700: `#733f05`,
primary_800: `#5c3204`,
primary_900: `#422202`,
primary_950: `#2a1501`,
...commonColors
},
dark: {
primary_50: `#2a1501`,
primary_100: `#422202`,
primary_200: `#5c3204`,
primary_300: `#733f05`,
primary_400: `#8b5106`,
primary_500: `#a16207`,
primary_600: `#d8902d`,
primary_700: `#ecb566`,
primary_800: `#f5cf9c`,
primary_900: `#fbe8cd`,
primary_950: `#fdf4e6`,
...commonColors
},
};
const appTheme = {COLORS};
export default appTheme;
Instead of adding the theme color and colors to each screen individually, a context structure needed to be created to access it from anywhere. The ThemeContext.js file was designed to use the application theme if it was previously set within the application. If it's opening for the first time or hasn't been selected before, the device's theme will be used. To store the application's theme, we saved it in AsyncStorage, or the device's memory, with the themeMode key. The application's theme is changed with the setTheme function. By storing it in useEffect, it ensures that it will be re-rendered if it changes. As seen in line 30, if it changes, the device's memory is updated and saved.
ThemContext.js
import React, { createContext, useContext, useEffect, useState } from `react`;
import { Appearance } from `react-native`;
import {COLORS as colors} from "../../constants";
import AsyncStorage from `@react-native-async-storage/async-storage`;
const ThemeContext = createContext();
export const THEME_MODES = {
LIGHT: `light`,
DARK: `dark`,
SYSTEM: `system`
};
export const ThemeProvider = ({ children }) => {
const [themeMode, setThemeMode] = useState(THEME_MODES.SYSTEM);
const [systemColorScheme, setSystemColorScheme] = useState(
Appearance.getColorScheme() || `light`
);
useEffect(() => {
(async () => {
const savedTheme = await AsyncStorage.getItem(`themeMode`);
if (savedTheme) {
setThemeMode(savedTheme);
}
})();
}, []);
useEffect(() => {
AsyncStorage.setItem(`themeMode`, themeMode);
}, [themeMode]);
useEffect(() => {
const subscription = Appearance.addChangeListener(({ colorScheme }) => {
setSystemColorScheme(colorScheme || `light`);
});
return () => subscription?.remove();
}, []);
const getActiveTheme = () => {
if (themeMode === THEME_MODES.SYSTEM) {
return systemColorScheme;
}
return themeMode;
};
const activeTheme = getActiveTheme();
const isDark = activeTheme === `dark`;
const currentColors = {
...colors.light,
...colors[activeTheme]
};
const contextValue = {
themeMode,
activeTheme,
isDark,
systemColorScheme,
colors: currentColors,
setTheme: setThemeMode,
toggleTheme: () => {
setThemeMode(current =>
current === THEME_MODES.LIGHT
? THEME_MODES.DARK
: THEME_MODES.LIGHT
);
},
setSystemTheme: () => setThemeMode(THEME_MODES.SYSTEM)
};
return (
{children}
);
};
export const useTheme = () => {
const context = useContext(ThemeContext);
return context;
};
export const useThemeColors = () => {
const { colors } = useTheme();
return colors;
};
Once the context structure for the theme is ready, you can see how we wrapped it in App.js. By locating it in the outermost layer, the screens are accessible.
- const { isDark } = useTheme(); With the definition here, the theme of the application is determined and kept in the state.
- const colors = useThemeColors();Here, it is defined like this to access colors. When color is called, it is read from the values under light or dark, depending on the theme of the application.
App.js
import React from `react`;
import {DarkTheme, NavigationContainer} from `@react-navigation/native`;
import StackNavigation from `./navigation`;
import {LogBox} from `react-native`;
import {DefaultTheme, PaperProvider} from `react-native-paper`;
import {ThemeProvider ,useTheme, useThemeColors} from "./screens/context/ThemeContext";
const AppContent = () => {
const { isDark } = useTheme();
const colors = useThemeColors();
const CustomLightTheme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
background: colors.neutrals_50,
},
};
const CustomDarkTheme = {
...DarkTheme,
colors: {
...DarkTheme.colors,
background: colors.neutrals_950,
},
};
return (
)
}
const App = () => {
console.warn = () => {};
LogBox.ignoreAllLogs(true);
return (
);
};
export default App;
Below, if the theme is changed within the application, the relevant config settings are made via the context and the application theme is changed.
setTheme(value ? THEME_MODES.DARK : THEME_MODES.LIGHT)}
value={isDark}
styleContainer={{ alignSelf: `center` }}
size={8}
/>
The callback procedure for style files is shown below. You can see how colors is called. By sending colors as parameters to the relevant style files, the relevant color is selected based on the theme type in the relevant file.
const colors = useThemeColors();
const styles = createStyles(colors);
As in the example below, colors are sent as parameters to the style file. This way, the color values are set to different color codes for light and dark themes, depending on the theme type.
styles.js
import { StyleSheet} from `react-native`;
const createStyles = (colors) => StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.primary_500,
},
});
export default createStyles;
The app is generally configured this way. I've tried to display light and dark themes in React Native. If you have any questions, please contact me.