How to implement dark mode in React
Tags: react, javascript, localstorage
Published: 2021-04-10 13:55:58

You might have seen in recent times that people are shifting towards dark mode, be it mobile screens,
the browser or you favorite social media app. You might be wondering how can you implement the same in react in your website.
In this tutorial, we will see how we can have a toggle button and change the theme of the page when the user clicks on it.
We will also see how we can read the browser theme (dark/light) and load the page based on that theme.
Also, we would see how the user preference can be saved so that we can retain the theme settings for future visits.


# Creating the app

First of all, as usual, let's create a react app using the following command

```bash
npx create-react-app dark-theme
```

# Basic page setup

Let's set up a sample HTML page for the demonstration.
Update the `App.js` file with the following code.
You could see that we have created a navbar that has a toggle button,
which will be used to switch the theme and a heading and a couple of paragraphs of lorem ipsum text.

```jsx
import React from "react"
import "./App.css"

function App() {
  return (
    <div className="App">
      <nav className="navigation">
        <div className="logo">Dark Mode</div>
        <button className="toggle_btn">Toggle</button>
      </nav>
      <h1>Lorem ipsum dolor sit amet consectetur adipisicing elit.</h1> <p>
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Rem, placeat
        adipisci aut repudiandae molestias quis possimus dignissimos tenetur
        tempore numquam, eos, sed deleniti quae voluptas asperiores harum labore
        ab deserunt? Perspiciatis, quisquam totam sapiente dolore cum, officiis
        veritatis sed ut quidem corrupti animi! Officiis animi quaerat officia
        ducimus, eveniet magnam delectus cupiditate amet vero aspernatur
        perferendis dolorem dignissimos praesentium vitae. Architecto dolorem
        eius distinctio nostrum fugit! Quas molestias, unde possimus vitae
        totam, quam eum earum est inventore harum aperiam praesentium sapiente
        repellat minima dolor corrupti eligendi, tempore reprehenderit animi
        delectus. Perferendis, et maxime reprehenderit possimus numquam
        corrupti, libero sed veniam optio vel a voluptates? Vel deserunt a animi
        saepe, dolores consequatur obcaecati ratione odio, ducimus repellendus
        aperiam error, laborum sed. Aspernatur excepturi vitae sint doloremque
        unde ipsa veniam placeat debitis? Aspernatur reprehenderit quibusdam
        pariatur fuga numquam voluptate magni praesentium optio nisi repellat
        placeat maxime at similique, provident, consequuntur, corrupti adipisci!
      </p>
      <p>
        Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis tempora
        maiores fugiat neque doloribus illum omnis expedita aliquam voluptatum
        possimus ad mollitia laudantium, non cumque quia, illo tempore odit
        veniam! Nisi enim, eligendi error quod dicta sunt fugit non et. Repellat
        corporis officiis odio repudiandae doloremque similique quisquam dicta
        enim, porro sed assumenda architecto iste accusantium quo quod, in
        incidunt? Eaque ipsum, id commodi reprehenderit quam exercitationem ad
        iure a cum necessitatibus corporis quas, odit, deserunt atque reiciendis
        deleniti fuga et laudantium officia adipisci. Voluptates, nesciunt!
        Repellendus consequuntur voluptate vero? Officia quaerat voluptates
        dolorem provident excepturi expedita nostrum, voluptas consequatur
        architecto. Vel recusandae officia quidem impedit magni cupiditate?
        Deserunt qui velit totam dolorem delectus necessitatibus possimus
        explicabo veritatis doloremque sequi. Optio, quod quaerat fugiat
        recusandae officia earum voluptatem aliquam unde obcaecati laborum
        necessitatibus porro omnis laboriosam esse, illum numquam quibusdam
        magnam. Voluptate et nesciunt quisquam sequi perferendis minus quaerat
        temporibus!
      </p>
    </div>
  )
}

export default App
```

Now add some basic styling in the `index.css` file.
You might notice that css variables are made use of, which will come handy in the upcoming sections.

```css
body {
  margin: 1rem auto;
  max-width: 800px;
  background-color: #fff;
  color: #000;
  --button-text: #000;
  --button-bg: #fff;
}

.toggle_btn {
  background-color: var(--button-bg);
  color: var(--button-text);
  cursor: pointer;
}
.navigation {
  display: flex;
  justify-content: space-between;
}
.logo {
  font-size: 1.2rem;
  font-weight: 600;
}
```

Now run the app by using `yarn start` command and opening [http://localhost:3000](http://localhost:3000).
You should be able to see a similar page given below:

![Basic Setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ck2vtffya0xwt3f5ntg3.png)

Now clicking on toggle button will not do anything. Let's make it work!

# The useDarkMode Hook

We will be writing a custom hook in order to implement the dark mode functionality.
So let's create a folder named `hooks` inside the `src` directory and create a file called `useDarkMode.js`

Update the file with the following code:

```jsx
import { useEffect, useState } from "react"

export default () => {
  const [isDark, setIsDark] = useState(false)

  useEffect(() => {
    const className = "dark"
    if (isDark) {
      window.document.body.classList.add(className)
    } else {
      window.document.body.classList.remove(className)
    }
  }, [isDark])

  return [isDark, setIsDark]
}
```

You could see that in the above code, we are initializing a local state variable using `useState` hook and defaulting it to false.
This state will determine if dark mode is enabled or not. Also, we are making use of `useEffect` hook,
where we are checking if the `isDark` state is set true or false and adding/removing the class named `dark` from the body of the document.
You could also see that we have added `isDark` as a dependency to the `useEffect` hook,
such that the effect runs only when the value of the `isDark` state changes.

## Making use of the useDarkMode hook

Now let's make use of the hook we have created, in the `App.js` and binding it with the button click:

```jsx
import React from "react"
import "./App.css"
import useDarkMode from "./hooks/useDarkMode"

function App() {
  const [isDarkMode, setDarkMode] = useDarkMode()
  return (
    <div className="App">
      <nav className="navigation">
        <div className="logo">Dark Mode</div>
        <button className="toggle_btn" onClick={() => setDarkMode(!isDarkMode)}>
          Toggle
        </button>
      </nav>
      <h1>Lorem ipsum dolor sit amet consectetur adipisicing elit.</h1>
      ...
    </div>
  )
}

export default App
```

In the above code you will see that each time the user clicks on the toggle button,
we are calling `setDarkMode` with a negated value of `isDarkMode`, so that it will be set to `true` and `false` in successive clicks.

Now, if you try to click on the toggle button, you may not see any changes happening.
But, if you inspect the document and see, you will see the class `dark` being added and removed.

![Dark class added to body](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mq79ylngue6e4apt103r.png)

# Adding styles to dark mode

Now that we have added `dark` class to the body, we can use the css to change the background and font colors to establish the dark mode.
Add the following rules to `index.css`

```css
body.dark {
  background-color: #000;
  color: #fff;
  --button-text: #fff;
  --button-bg: #000;
}
```

In the above styles, we are setting the background color to black and text color to white whenever `body` has the class `dark`.
Also, you will see that we are making use of css variables to control the styles of the button.

If you click on the toggle button now, you should be able to see the theme getting toggled:

![Animation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8sxogqogidax7664mrkk.gif)

# Storing user preference in Local Storage

As a user, you might want your choice of theme to be remembered and retained in that mode whenever you revisit the page in the future.
Currently, if you set to dark mode and reload the page, the page will load in light mode.
To preserve the mode, we will store the user preference in Local Storage.

Update `useDarkMode.js` with the following code

```jsx
import { useEffect, useState } from "react"

export default () => {
  const key = "isDarkMode"
  const [isDark, setIsDark] = useState(() => {
    try {
      // Get from local storage by key
      const item = window.localStorage.getItem(key)
      // JSON.parse converts from Sting to Boolean
      return item ? JSON.parse(item) : undefined
    } catch (error) {
      // If error return false, i.e, light mode
      return false
    }
  })

  useEffect(() => {
    const className = "dark"
    if (isDark) {
      window.document.body.classList.add(className)
    } else {
      window.document.body.classList.remove(className)
    }
    try {
      window.localStorage.setItem(key, isDark)
    } catch (e) {
      console.error("Error in setting preference")
    }
  }, [isDark])

  return [isDark, setIsDark]
}
```

If you see in the above code, we are using a callback for initializing the `isDark` state and in the callback, we are accessing the local storage to fetch the preference of the user. When the user toggles the state, we save the preference to the local storage in the `useEffect` callback.

If you set the preference to dark mode and reload the page, you would see that the page loads in dark mode.

# Reading browser theme/settings

Most of the modern browsers support media query called [prefers-color-scheme](https://caniuse.com/#search=prefers-color-scheme),
using which we can determine if the user prefers dark mode or light mode.
We can make use of the [Window.matchMedia()](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia)
method to query the value of `prefers-color-scheme`, as highlighted in the code below.
Also, note that we have introduced a new variable `darkModeEnabled`, which now stores the preference value.

```jsx
import { useEffect, useState } from "react"

export default () => {
  const key = "isDarkMode"
  const [isDark, setIsDark] = useState(() => {
    try {
      // Get from local storage by key
      const item = window.localStorage.getItem(key)
      // JSON.parse converts from Sting to Boolean
      return item ? JSON.parse(item) : undefined
    } catch (error) {
      // If error return false, i.e, light mode
      return false
    }
  })

  // Check if user has any preference in the local storage.
  // If not then load the system preference

  const darkModeEnabled =
    typeof isDark !== "undefined"
      ? isDark
      : window.matchMedia("(prefers-color-scheme: dark)").matches
  useEffect(() => {
    const className = "dark"
    if (darkModeEnabled) {
      window.document.body.classList.add(className)
    } else {
      window.document.body.classList.remove(className)
    }
    try {
      window.localStorage.setItem(key, darkModeEnabled)
    } catch (e) {
      console.error("Error in setting preference")
    }
  }, [darkModeEnabled])

  return [darkModeEnabled, setIsDark]
}
```

Now if your system set into dark mode, then by default the page will open in dark mode.

# Displaying different Icons for each mode

Now let's show a separate icon for both dark and light mode.
We will make use of [react-icons](https://react-icons.github.io/react-icons/) for this purpose.

Install react-icons using the following command:

```bash
yarn add react-icons
```

Once the installation completes, make use of the `BsSun` and `BsMoon` icons to represent the light and dark mode respectively:

```jsx
import React from "react"
import "./App.css"
import useDarkMode from "./hooks/useDarkMode"
import { BsMoon, BsSun } from "react-icons/bs"

function App() {
  const [isDarkMode, setDarkMode] = useDarkMode()
  return (
    <div className="App">
      <nav className="navigation">
        <div className="logo">Dark Mode</div>
        <button className="toggle_btn" onClick={() => setDarkMode(!isDarkMode)}>
          {isDarkMode ? (
            <BsSun color="#ff0" size="24" title="Switch to light mode" />
          ) : (
            <BsMoon size="24" title="Switch to dark mode" />
          )}
        </button>
      </nav>
      <h1>Lorem ipsum dolor sit amet consectetur adipisicing elit.</h1>
      ...
    </div>
  )
}

export default App
```

Finally, update the CSS file to remove the border from the button

```css
...
.toggle_btn {
  background-color: var(--button-bg);
  color: var(--button-text);
  cursor: pointer;
  border: none;
}
...
```

Now load the page and you should be able to see the icons added!

![Dark Mode with Icon](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cz4n4tzdhv8xfnvgyjhj.gif)

# Source code and Demo

You can view the complete [source code here](https://github.com/collegewap/react-dark-mode) and a [demo here](https://react-dark-mode-mu.vercel.app/)