DEV Community

Gustavo Gomez
Gustavo Gomez

Posted on

Manage Dark Mode in ReactJs, With Listener to Detect Dark-Mode in the Browser

Nowadays most web/mobile applications have a dark mode presentation. That's why today I will explain to you how to do it in reactJs applications. There are several ways to achieve it, I will do it using CSS and HTML.

In Addition, I'm going to add a listener to detect if the dark/light mode of your browser has changed, so the app will change styles automatically.

1. Create a react app

Choose a location in your terminal and run this:



npx create-react-app my-dark-mode-app


Enter fullscreen mode Exit fullscreen mode

2. Add CSS

Let's modify some CSS. First, we are going to create some global css variables to use. Go to index.css and add this lines at the beginning of the file:



:root {
  --background-color: #fefefe;
  --text-color: #3f3f3f;
}

.dark-mode {
  --background-color: #3f3f3f;
  --text-color: #ffffff
}


Enter fullscreen mode Exit fullscreen mode

In the same, file add these properties to body:



background-color: var(--background-color);
color: var(--text-color);


Enter fullscreen mode Exit fullscreen mode

Those previous styles will help us to change dark mode styles: background color and text colors.

Now go to the file App.css and make sure you just have these styles:



.App {
  text-align: center;
}

.App-header {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
}


Enter fullscreen mode Exit fullscreen mode

3. Modify JavaScript

We are going to work with a simple view, so go to App.js and edit the return:



return (
  <div className="App">
    <header className="App-header">
      <p>
        This is an example of dark/light mode using reactJs !!
      </p>
    </header>
  </div>
);


Enter fullscreen mode Exit fullscreen mode

4. Change Dark Mode

Inside App.js we should create a useState hook to save the mode of our application:



const [mode, setMode] = useState('light')


Enter fullscreen mode Exit fullscreen mode

Don't forget to import useState



import {useState} from "react";


Enter fullscreen mode Exit fullscreen mode

Also, with this new method we're going to save in our state the current mode, then add or remove the dark-mode classname of the body tag.



const onSelectMode = (mode) => {
  setMode(mode)
  if (mode === 'dark')
    document.body.classList.add('dark-mode')
  else
    document.body.classList.remove('dark-mode')
}


Enter fullscreen mode Exit fullscreen mode

Then, in this same file let's add some icons after the p tag:



<div>
  {
    mode === 'dark' ?
      <svg className={'icons'} xmlns="http://www.w3.org/2000/svg" width={33} viewBox="0 0 24 24" fill="#f1e408"
           onClick={() => onSelectMode('light')}>
        <rect fill="none"/>
        <path
          d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"/>
      </svg> :
      <svg className={'icons'} xmlns="http://www.w3.org/2000/svg" width={33} viewBox="0 0 24 24" fill="#000000"
           onClick={() => onSelectMode('dark')}>
        <rect fill="none" height="24" width="24"/>
        <path
          d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/>
      </svg>
  }
</div>


Enter fullscreen mode Exit fullscreen mode

As you can see, we are calling the onSelectMode method with the mode as parameter. I'm showing a sun icon if we are in dark mode, and a moon icon if light mode is active. Clicking in those icons we can change our mode.

Now let's add a missing style in App.css:



.icons {
  cursor: pointer;
}


Enter fullscreen mode Exit fullscreen mode

So far our App.js looks like this:



import './App.css';
import {useState} from "react";

function App() {
  const [mode, setMode] = useState('light');

  const onSelectMode = (mode) => {
    setMode(mode)
    if (mode === 'dark')
      document.body.classList.add('dark-mode')
    else
      document.body.classList.remove('dark-mode')
  }

  return (
    <div className="App">
      <header className="App-header">
        <p>
          This is an example of dark/light mode using reactJs !!
        </p>
        <div>
          {
            mode === 'dark' ?
              <svg className={'icons'} xmlns="http://www.w3.org/2000/svg" width={33} viewBox="0 0 24 24" fill="#f1e408"
                   onClick={() => onSelectMode('light')}>
                <rect fill="none"/>
                <path
                  d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"/>
              </svg> :
              <svg className={'icons'} xmlns="http://www.w3.org/2000/svg" width={33} viewBox="0 0 24 24" fill="#000000"
                   onClick={() => onSelectMode('dark')}>
                <rect fill="none" height="24" width="24"/>
                <path
                  d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/>
              </svg>
          }
        </div>
      </header>
    </div>
  );
}

export default App;


Enter fullscreen mode Exit fullscreen mode

Until this point, we are able to change manually our dark/light mode. What if we want that our app automatically detects if dark/light mode of our devices has changed?.

Let's do it!

5. Adding a Listener

First, we need to know how we can detect if dark/light mode has changed in our browser(don't copy yet):



window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => onSelectMode(e.matches ? 'dark' : 'light'));


Enter fullscreen mode Exit fullscreen mode

The e.matches will return true if dark mode has been enabled.

Let's create a useEffect hook, to create our listener the first time our app loads.



useEffect(() => {
  // Add listener to update styles
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => onSelectMode(e.matches ? 'dark' : 'light'));

  // Setup dark/light mode for the first time
  onSelectMode(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')

  // Remove listener
  return () => {
    window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', () => {
    });
  }
}, []);


Enter fullscreen mode Exit fullscreen mode

We must know that the Listener is going to detect when dark mode changes. However, the first time our app loads we need to know the current mode of our browser, that's why I added this line in our useEffect:



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


Enter fullscreen mode Exit fullscreen mode

image1
image1

Conclusion

We created a reactJs application, in which we were able to implement dark mode using CSS to change the background color and text color.
Every text inside the body tag will be changed in dark/light mode. Then we recognize the browser mode to show our page in dark or light mode automatically.

You can see the demo: dark-mode-reactjs-demo

Github repo:

Top comments (4)

Collapse
 
efeligne profile image
efeligne • Edited

Thanks for the post. It's helpful.

I'd like to suggest to rename the "mode" param that is defined in "onSelectMode" function. The param has the same name as state. It's a little bit confusing.

Have a nice day!

Collapse
 
gustavogomez profile image
Gustavo Gomez

Hey! yeah I didn’t see that, thanks !

Collapse
 
longwater1234 profile image
Davis Tibbz

Thanks!

Collapse
 
khamdamov profile image
Doston Khamdamov • Edited

So matchMefia function detects the color theme based on WHAT? Color, Class, MediaQuery or System?