When it comes to Gatsby, there are several unique hacks you should be aware of in order to successfully create a dark mode for your application. In this post, we will go through the whole implementation from start to finish.
Prerequisites
- The Gatsby CLI installed
Getting started
Add the simple command below to create a new site.
npm init gatsby
💡 Note - Continue to follow the prompts to select your desired language (JavaScript or TypeScript), CMS, styling tools, and other features.
When everything has been downloaded, you will get a notice with instructions on how to navigate to your site and execute it locally.
// Start by going to the directory with
cd my-gatsby-site
// Start the local development server with
npm run develop
💡 Note - Navigate to localhost:8000 and you should see your app running. Edit the home page in
src/pages/index.js
, save it, and reload the page to see your live changes in the browser.
Part 1 : Adding CSS Variables
The aim is to use CSS variables and React's UseState hook. Any alternative state manager will also work, but for the sake of simplicity, we'll simply employ the standard UseState hook.
We'll start building the CSS for dark mode.
/* 1 */
:root {
--color-primary: #CD104D;
--color-text: #2e353f;
--color-text-light: #4f5969;
--color-heading: #1a202c;
--color-heading-black: #000000;
--color-accent: #d1dce5;
--color-background: #FFFFFF;
--dark-mode: #000;
--dark-mode-hf: #CD104D;
}
/* 2 */
body {
color: var(--color-text);
background-color: var(--color-background);
}
💡 Note - By adding these variables to the
body
element, they become available to all of our components and classes globally.
Next, we'll add values to the dark and light mode variables. We will accomplish this by defining a class.
/* 3 */
body.dark {
--color-background: #171717;
--color-primary: #CD104D;
--color-text: #ffffff;
--color-text-light: #4f5969;
--color-heading: #DFDFDE;
--color-heading-black: #EDEDED;
--color-accent: #d1dce5;
--image: url("./images/sun.svg");
}
/* 4 */
body.light {
--color-primary: #CD104D;
--color-text: #2e353f;
--color-text-light: #4f5969;
--color-heading: #1a202c;
--color-heading-black: black;
--color-accent: #d1dce5;
--color-background: #FFFFFF;
--image: url("./images/moon.svg");
}
💡 Note - We have now defined the themes for the light and dark modes.
Part 2 : Adding a switch button for toggling between dark and light themes
We'll have a toggle
component that appears on all of our pages, hence it seems reasonable to have the switch button there.
Here’s how the toggle
component looks like:
import React from "react"
import "../style.scss"
import Sun from "../images/sun.svg"
import Moon from "../images/moon.svg"
export default function DarkMode() {
const [isDark, setIsDark] = React.useState(false)
React.useEffect(() => {
if (isDark) {
document.body.classList.add('dark');
} else {
document.body.classList.remove('dark');
}
}, [isDark])
return (
<div className="global-toggle-switch">
<span>
{isDark ? (<img onClick={() => setIsDark(!isDark)} src={Sun} alt="sun img" />) : (<img onClick={() => setIsDark(!isDark)} src={Moon} alt="moon img" />)}
</span>
</div>
)
}
💡 Note - These icons function as buttons, and when selected, they either add or delete the
.dark
class from the body, changing the app's theme.
Part 3 : Saving the default theme to the local storage
Now we also need to store the preferred mode to local storage, so that it will remain even if the page or application is reset. To accomplish this, I create a global utility function called getDefaultTheme
:
/* 5 */
function getDefaultTheme() {
const savedTheme = window.localStorage.getItem('theme');
return savedTheme ? savedTheme : 'light';
}
💡 Note - This method must now be invoked within the useState hook created within my DarkMode component.
/* 6 */
const [isDark, setIsDark] = React.useState(getDefaultTheme())
To do this, I return to the useEffect hook and insert the following code:
/* 7 */
React.useEffect(() => {
if (isDark === 'dark') {
document.body.classList.add('dark');
} else {
document.body.classList.remove('dark');
}
window.localStorage.setItem('theme', isDark);
}, [isDark])
If you followed along, your completed code should look like this:
import React from "react"
import "../style.scss"
import Sun from "../images/sun.svg"
import Moon from "../images/moon.svg"
function getDefaultTheme() {
const savedTheme = window.localStorage.getItem("theme")
return savedTheme ? savedTheme : "light"
}
export default function DarkMode() {
const [isDark, setIsDark] = React.useState(getDefaultTheme())
React.useEffect(() => {
if (isDark === "dark") {
document.body.classList.add("dark")
} else {
document.body.classList.remove("dark")
}
window.localStorage.setItem("theme", isDark)
}, [isDark])
return (
<div className="global-toggle-switch">
<span onClick={() => setIsDark(isDark === "dark" ? "light" : "dark")}>
{isDark === "dark" ? (
<img src={Sun} alt="sun img" />
) : (
<img src={Moon} alt="moon img" />
)}
</span>
</div>
)
}