Algobook
- The developer's handbook
mode-switch
back-button
Buy Me A Coffee
Sun May 14 2023

How to create dark mode theme with a toggle in a React application

Nowadays, almost all websites and apps have a toggle for shifting the theme of the application between light and night mode. Reading in the evening with a full blast of white light is really annoying for the eyes and also have negative health effects, for example bad sleep.

I got a question a while back on how I did our toggle here at Algobook, and I thought I would share how I solved it. Basically, we are using React context as our state handler for the theme toggling, and localStorage for saving the last theme to get it persistent.

React Context

I wrote an article on React context and how to use it if you are interested but I will show it in this guide as well, you can find it here if you want a more in-depth guide.

But let's start with creating our context. Create a file called ThemeContext.tsx or .jsx if you are not using TypeScript. If you want to start using TypeScript in your React app, read this guide.

import { createContext, useState } from "react"; export const ThemeContext = createContext({ isNightMode: false, dispatch: (isNightMode: boolean) => {}, }); interface IThemeContextProviderProps { children: JSX.Element | JSX.Element[]; } export const ThemeContextProvider = ({ children, }: IThemeContextProviderProps) => { const [isNightMode, setNightMode] = useState(false); const dispatch = (isNightMode: boolean) => setNightMode(isNightMode); return ( <ThemeContext.Provider value={{ isNightMode, dispatch }}> {children} </ThemeContext.Provider> ); };

So, this is our state handler. What this basically does is, that it will use createContext from React and create our ThemeContext, and we will initialize it with a boolean prop isNightMode and a dispatch function that will accept a boolean. This is our skeleton of what our context will return to the consumer basically.

And then we are creating our ThemeContextProvider that will do the actual logic. We will create a state which will hold our isNightMode and also creacting our dispatch function that is setting the new state of the theme. And then we will return the isNightMode and dispatch function with our <ThemeContext.Provider> and wrapping the consuming children.

Consume the context

If you haven't worked a lot with context, the above example can look a little bit strange. But I hope we can sort that our in below examples.

First step is to wrap the part of the application that should get access to the context. In our case, we want the whole application to get access to it since we want to change all colors etc depending on the state.

In our index.tsx (index.jsx), wrap everything with our context provider. It should look something like this:

import { ThemeContextProvider } from "./contexts/ThemeContext"; const root = ReactDOM.createRoot( document.getElementById("root") as HTMLElement ); root.render( <React.StrictMode> <ThemeContextProvider> <App /> </ThemeContextProvider> </React.StrictMode> );

And now everything that is wrapped within <ThemeContextProvider> will get access to the theme.

Set the theme variable

If we want to set the new value of our context, for example in a toggle or like in our case for this website, where we start by reading the value from localStorage we will do as follows

import { useContext } from "react"; import { ThemeContext } from "./contexts/ThemeContext"; const App = () => { const { dispatch } = useContext(ThemeContext); useEffect(() => { const isNight = localStorage.getItem("isNightMode") === "true"; dispatch(isNight); }, []); return <div>App component</div>; };

Now we will grab dispatch from the ThemeContext and call it when the component is mounted.

A toggle function could look like this for example. Note that we now also are reading the isNightMode value from the context. It will be changed on every dispatch, so it will always be the current value. And all components that are using the context, will re-render.

import { useContext } from "react"; import { ThemeContext } from "./contexts/ThemeContext"; const Component = () => { const { isNightMode, dispatch } = useContext(ThemeContext); const onThemeClick = () => { dispatch(!isNightMode); }; return <div onClick={onThemeClick}>Change theme</div>; };

Styling

Then we have the styling part.. This can be a big job if you have an existing application with a lot of styling that needs to be altered. If you are lucky, you are in the beginning of your journey, and then it will be much easier.

What I have done in Algobook, is to change the main styles, for example whole app background, all links, all h1, h2 and so on - in our App component.

So by reading the isNightMode, I will toggle a class name.

import styles from "./App.module.scss"; const { isNightMode } = useContext(ThemeContext); <div className={`${styles.app} ${isNightMode ? styles.night : null}`}> <Application /> </div>;

And then styling would be something like below snippet. I am applying a little fade in animation for a little bit more smooth transition as well.

.night { background-color: #444 !important; color: #fffdf2; h1 { color: #fffdf2 !important; } a { color: #77b1e3 !important; } input { background-color: #ddd; color: white; } animation: fadeInAnimation ease 0.5s; animation-iteration-count: 1; animation-fill-mode: forwards; } @keyframes fadeInAnimation { 0% { opacity: 0; } 100% { opacity: 1; } }

And then I also have additional checks in some of the components where it needed to be a little bit customized, for example with some box shadows and borders et cetera. But the foundation is that I am checking the isNightMode like above example, and then applying the correct class names to the elements.

Outro

In this article, I shared how we can achieve dark mode in a React application using React context. I guess there are many ways of solving this, the methods in this guide is how we did it for this website which I think is quite smooth. It require however that there are checks in quite a few places for applying the correct styling, and this will be even more if the application is older.

I hope you enjoyed this article, and I hope even more that it could help you with your dark mode feature in your application.

All the best,

signatureSun May 14 2023
See all our articles