By Hemanta Sundaray on 2021-06-20
There are many ways you can implement a light and dark mode toggle in React. I prefer using GSAP (a JavaScript animation library), because:
Now, let’s take a look at what we are going to build.
This is how the page looks in light mode.
When you click on the toggle button, the dark mode is activated.
The sun and moon icons inside the toggle button are from Font-Awesome.
All right, let’s start building the project.
Create a folder called mode, open the folder in VS Code and create a React project using the following command:
PS C:\Users\Delhivery\Desktop\mode> npx create-react-app .
Let’s install gsap, which is available as a package from npm.
PS C:\Users\Delhivery\Desktop\mode> npm i gsap
We will use Font Awesome icons using Font Awesome’s official React component.
We need to install the following three packages:
PS C:\Users\Delhivery\Desktop\mode> npm i --save @fortawesome/fontawesome-svg-core
@fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome
You can find more about using Font Awesome icons in React at the following link:
We have installed our required dependencies. Next, we will create the toggle button.
import React from "react"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faSun } from "@fortawesome/free-solid-svg-icons"
import { faMoon } from "@fortawesome/free-solid-svg-icons"
const App = () => {
return (
<div className="wrapper">
<div className="toggle">
<div>
<FontAwesomeIcon icon={faSun} className="sun" />
</div>
<div>
<FontAwesomeIcon icon={faMoon} className="moon" />
</div>
<div className="circle"></div>
</div>
<section className="intro">
<h1 className="intro-header">Accessibility</h1>
<p className="intro-text">
Accessibility means developing content to be as accessible as possible
no matter an individuals's physical and cognitive abilities and no
matter how they access the web.
</p>
</section>
</div>
)
}
export default App
/*_ || Global styling _*/
- {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html {
font-size: 62.5%;
font-family: sans-serif;
}
/*_ End of global styling _*/
/*_ || Toggle button styling _*/
.toggle {
width: 4rem;
height: 2rem;
margin: 4rem;
background-color: gray;
border-radius: 3rem;
position: relative;
cursor: pointer;
display: flex;
justify-content: space-around;
align-items: center;
}
.sun,
.moon {
font-size: 1.2rem;
}
.sun {
color: white;
}
.circle {
width: 1.4rem;
height: 1.4rem;
background-color: lightgray;
border-radius: 50%;
position: absolute;
top: 50%;
left: 5%;
transform: translateY(-50%);
box-shadow: 0 0.1rem 0.3rem rgba(0, 0, 0, 0.6);
}
/*_ End of toggle button styling _*/
/*_ || Text styling _*/
.intro {
width: clamp(20rem, 50vw, 80rem);
margin: auto;
}
.intro h1 {
font-size: clamp(2rem, 3vw, 6rem);
margin-bottom: 2rem;
}
.intro p {
font-size: clamp(1.3rem, 1.2vw, 2rem);
letter-spacing: 0.02rem;
line-height: 2.5rem;
color: #353935;
}
/*_ End of text styling _*/
Start the application using npm start and we have the following result:
To implement the toggle functionality, we will go through the following steps:
We will add an onClick handler to the div with the class name of toggle. In the onClick handler function, we will use JavaScript ternary operator to play the animation based on a condition: play the animation if it is reversed and reverse the animation if otherwise. Note that the animation will be in a reversed and paused state at the start.
When we play the animation, we will simultaneously animate three things:
Add the highlighted code snippets in the App.js file.
import React, { useRef, useEffect } from "react"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faSun } from "@fortawesome/free-solid-svg-icons"
import { faMoon } from "@fortawesome/free-solid-svg-icons"
import { gsap } from "gsap"
const animation = gsap.timeline({
paused: true,
reversed: true,
ease: "expo.inOut",
duration: 0.01,
})
const App = () => {
const wrapperRef = useRef()
useEffect(() => {
const circle = wrapperRef.current.querySelector(".circle")
const introHeader = wrapperRef.current.querySelector(".intro-header")
const introText = wrapperRef.current.querySelector(".intro-text")
animation
.to(circle, { x: "2rem" })
.to(document.body, { backgroundColor: "#212121" }, "<")
.to(introHeader, { color: "gray" }, "<")
.to(introText, { color: "gainsboro" }, "<")
})
const circleClickHandler = () => {
animation.reversed() ? animation.play() : animation.reverse()
}
return (
<div className="wrapper" ref={wrapperRef}>
<div className="toggle" onClick={circleClickHandler}>
<div>
<FontAwesomeIcon icon={faSun} className="sun" />
</div>
<div>
<FontAwesomeIcon icon={faMoon} className="moon" />
</div>
<div className="circle"></div>
</div>
<section className="intro">
<h1 className="intro-header">Accessibility</h1>
<p className="intro-text">
Accessibility means developing content to be as accessible as possible
no matter an individuals's physical and cognitive abilities and no
matter how they access the web.
</p>
</section>
</div>
)
}
export default App
That’s all we need to do. Now, if you click on the toggle button, dark mode gets activated. And if you click on the toggle button again, we go back to light mode.