Working with theme context in React

It's becoming harder to see a webapp, website or native app without light and dark modes.

Theming is now a must have for projects, clients are asking for it, UI designers are making designs for dark and light modes for every page/state in the project.

  • Is applying themes in a project a big deal?
  • Is it hard?
  • I just started writing react, and I don't even know how to use themes in vanilla js and you are saying i should use it for this project.
  • How do i do this?

I'm confused

All these and more are the questions that plague the developer, I should know, because I asked same and more.

wink.

We will discuss an easy and straightforward way of implementing themes in react apps using the context api.

First Initialize the project

npx create-react-app theme-app

Then, install the dependencies;

  • react-router-dom,
  • any other you want...

Create a themeContext.js file

This is where we write our theme context for both light and dark modes or the plethora of modes we want. Also, our colours go here.

In this file, because we are using functional and not class components, we import react's useState hook to handle theme mode.

import React, { useState } from 'react'

Set color for theme states

Then, write a themes object that contains all the themes you want to use. Since we are only using light and dark modes, we will create our themes object as:

const themes = {
  light: {
    background: '#ffffff',
    lightFont: '#716E8B',
    primaryFont: '#22202D',
    secondaryFont: '#393649',
    buttonFont: '#442ECF',
 },
 dark: {
    background: '#222222',
    lightFont: '#B4B0D1',
    primaryFont: '#ffffff',
    buttonFont: '#ffffff',
    secondaryFont: '#F5F4FA',
 },
};

Get user's preferred theme

Next, we declare our defaultTheme. This will be used to set the theme to either light or dark based on the user's presets.

let defaultTheme;

Then check what theme mode user prefers using

if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
  defaultTheme = true;
} else {
 defaultTheme = false;
}

This checks, if the user prefers the dark mode.

window.matchMedia('(prefers-color-scheme: dark)').matches 

returns either true if the user has set his device to dark mode or false if the user has set his device to light mode instead. If the mode is dark, we set defaultTheme to true. we'll understand why soon.

Create themeContext

export const themeContext = React.createContext();

This creates our context and exports it so we can use it in other files to access our theme.

If you don't understand how contexts work in react, you can read the official react doc while i work on an article on the subject.

Write Theme.Provider

This is used to provide our theme context to descendant components.

export const ThemeProvider = props => {
  const [isDark, setIsDark] = useState(defaultTheme);

  let theme;
  if (isDark) {
    theme = themes.dark;
  } else {
    theme = themes.light;
  }

  const toggleTheme = () => setIsDark(!isDark);

  return (
    <themeContext.Provider value={{
      theme,
      toggleTheme,
      isDark
    }}>
       {props.children}
    </themeContext.Provider>
  )
}

The first thing we are doing is creating a state variable isDark and setting it to defaultTheme which is true if the user set his device to dark mode and false if the device is in light mode.

Hence, isDark is true if the device is set to dark mode and vice versa.

Then, we declare theme and checking if isDark is true, set theme to the dark object in the themes object or do the else, if isDark is false.

Next, we write a toggleTheme function to change isDark when called.

In, the return statement, we are using themeContext.Provider to pass theme, toggleTheme and isDark to descendants.

This is done by passing them as an object inside the value prop.

Note: the double curly braces

Lastly, pass the children in the Provider using props.children

Consume ThemeContext in our app

import React, { useContext } from 'react';
import { themeContext, ThemeProvider } from './themeContext';

const App = props => {
  const { theme, toggleTheme } = useContext(themeContext);

  return (
    <ThemeProvider>
       <section style={{
          background: theme.background,
          color: theme.primaryFont
        }}
       >
         <button onClick={toggleTheme}> Toggle Theme </button>
       </section>
    </ThemeProvider>
  )
}

In order to use our context, we have to utilize the useContext hook from react. Also, you must import the themeContext in any file you want to access the theme context in.

However, the ThemeProvider should just be placed at the highest level in your app.

We destructure our theme and the toggleTheme function from the themeContext.

Now, you can apply the theme using the style prop, styled-components for custom components and any other methods you had in mind.

You can also call the toggleTheme function to change the theme by clicking the button.

I believe your theme should be functional now.

If I missed anything or you have any questions, feel free to comment below.

I hope you are able to utilize theming now.

Cheers

Edidiong Asikpo's photo

Amazing article Shammah Nei. Thanks for sharing.

kris forsubscribe's photo

Thanks for this awesome article on changing themes from light to dark mode in React Native using Theme context. Dark themes are the modern-day app features that are getting more popular as many popular apps are adopting it. You can not only change the UI from light to dark but make an overall app theme look for beautiful by checking out some of the amazing React Native Themes available in the market.