DEV Community

loading...
Cover image for How to implement dark mode in React

How to implement dark mode in React

collegewap profile image collegewap Originally published at codingdeft.com ・7 min read

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

npx create-react-app dark-theme
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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.

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;
}
Enter fullscreen mode Exit fullscreen mode

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

Basic Setup

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:

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]
}
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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

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

body.dark {
  background-color: #000;
  color: #fff;
  --button-text: #fff;
  --button-bg: #000;
}
Enter fullscreen mode Exit fullscreen mode

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

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

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]
}
Enter fullscreen mode Exit fullscreen mode

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,
using which we can determine if the user prefers dark mode or light mode.
We can make use of the 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.

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]
}
Enter fullscreen mode Exit fullscreen mode

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 for this purpose.

Install react-icons using the following command:

yarn add react-icons
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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

...
.toggle_btn {
  background-color: var(--button-bg);
  color: var(--button-text);
  cursor: pointer;
  border: none;
}
...
Enter fullscreen mode Exit fullscreen mode

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

Dark Mode with Icon

Source code and Demo

You can view the complete source code here and a demo here

Discussion (0)

pic
Editor guide