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
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
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;
}
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:
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]
}
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
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.
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;
}
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:
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]
}
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]
}
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
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
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;
}
...
Now load the page and you should be able to see the icons added!
Source code and Demo
You can view the complete source code here and a demo here
Top comments (0)