In this article I will share my approach for the dark/light mode toggle that I recently implemented in a React project, which I think is quite easy to understand also for beginners.
First I add a <span>
element into my App.tsx
file. It can either be a <button>
, <div>
, whatever you prefer. This will act as a switch for dark/light mode :
import React, { useEffect, useState } from 'react';
function App() {
return (
<div className='container'>
<span className='mode-switch'></span>
{/* my other elements */}
</div>
)
}
export default App;
Then I add some basic styles. I prefer my switch to be positioned absolutely on the right top corner of my container element:
.container {
position: relative;
max-width: 1400px;
padding: 40px 30px;
}
.mode-switch {
position: absolute;
right: 15px;
top: 15px;
font-size: 11px;
cursor: pointer;
transition: color 0.2s ease-in-out;
&:hover {
color: #50bbf1;
}
}
I go back to my App
component and add the useState
hook. I define a mode
variable and a setMode
function. For now I pass the default mode as 'light' inside the useState
hook.
Then I add an onClick
event to my switch and in this event, I call the setMode
function with a conditional parameter.
This function makes sure that it sets the mode to dark if it was light, and vice versa.
I also add the text content into the switch dynamically :
function App() {
const [mode, setMode] = useState('light');
return (
<div className='container'>
<span
className='mode-switch'
onClick={() =>
setMode(mode === 'dark' ? 'light' : 'dark')
}
>
{mode === 'dark' ? 'Light mode' : 'Dark mode'}
</span>
</div>
)
}
Next step is switching between modes and adding/removing relevant styles, which will be achieved using the useEffect
hook.
It will simply add a '.dark' class to the <body>
when switched to the dark mode, and remove it when the selected mode is light.
I pass [mode]
as the second parameter to useEffect
because it will work as the side effect of the changing 'mode':
function App() {
const [mode, setMode] = useState('light');
useEffect(() => {
if (mode === 'dark') {
document.body.classList.add('dark');
} else {
document.body.classList.remove('dark');
}
}, [mode]);
return (
Then I add the necessary styles, which make the background-color black and turn all the text to white if they were not originally assigned any color and were black by default:
.dark {
background-color: #222;
color: #f5f5f5;
}
In order to style other elements other than the <body>
in dark mode, I use the &
selector.
Let's say I have a button with 'primary-button' class. I want to change its color and background-color when the dark mode is active:
.primary-button {
// default style: black button with white text
background-color: #222;
color: #f5f5f5;
// dark mode style: white button with black text
.dark & {
background-color: #f5f5f5;
color: #222;
}
}
Now it's time to save the selected mode to the local storage, so that the selected mode will persist even if the app is reset. To achieve this, first I go back to the useEffect
hook and include the following code into it:
useEffect(() => {
if (mode === 'dark') {
document.body.classList.add('dark');
} else {
document.body.classList.remove('dark');
}
localStorage.setItem('mode', mode); // mode saved to local storage
}, [mode]);
Then I go up and create a utility function called getDefaultMode
on a global level. This function will get the saved mode from the local storage and determine the default mode accordingly when the app starts. If dark mode was not selected previously, the default mode will be 'light':
function getDefaultMode() {
const savedMode = localStorage.getItem('mode');
return savedMode ? savedMode : 'light';
}
Now I need to call this function inside the useState
hook that I previously added inside my App
component. I replace the light
parameter with the getDefaultMode
function:
const [mode, setMode] = useState(getDefaultMode());
The final code looks like this in the end:
import React, { useEffect, useState } from 'react';
function getDefaultMode() {
const savedMode = localStorage.getItem('mode');
return savedMode ? savedMode : 'light';
}
function App() {
const [mode, setMode] = useState(getDefaultMode());
useEffect(() => {
if (mode === 'dark') {
document.body.classList.add('dark');
} else {
document.body.classList.remove('dark');
}
localStorage.setItem('mode', mode);
}, [mode]);
return (
<div className='container'>
<span
className='mode-switch'
onClick={() =>
setMode(mode === 'dark' ? 'light' : 'dark')
}
>
{mode === 'dark' ? 'Light mode' : 'Dark mode'}
</span>
{/* my other elements */}
</div>
)
}
Top comments (7)
This is good, however you might have to use the css variables instead of maintaining a child class for each and every style class.
Great suggestion, I will definitely look into that. Thank you!
Is it a valid approach? Do you have example app or website that you implemented this aproach?
Hi Ulan,
Yes I recently implemented it in a Pomodoro timer application. The project is not live yet since it is still work in progress, but the source code is visible on my github: github.com/etunka/leila-tomato-tim...
Now it's live :)
leila-tomato-timer.netlify.app/
Thanks. It would be useful if you make it a chrome extension rather than an app.
good, keep up. nice work but if you include bootstrap to toggle the mode that will also good though keep up your work.