Dark Mode with Gatsbyjs for Dummies

Helitha Rupasinghe - Oct 25 '22 - - Dev Community

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

Getting started

Add the simple command below to create a new site.



npm init gatsby


Enter fullscreen mode Exit fullscreen mode

💡 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


Enter fullscreen mode Exit fullscreen mode

💡 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);
}


Enter fullscreen mode Exit fullscreen mode

💡 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");
}



Enter fullscreen mode Exit fullscreen mode

💡 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>
  )
}


Enter fullscreen mode Exit fullscreen mode

💡 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.

Dark.gif

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';
}


Enter fullscreen mode Exit fullscreen mode

💡 Note - This method must now be invoked within the useState hook created within my DarkMode component.



/* 6 */
const [isDark, setIsDark] = React.useState(getDefaultTheme())


Enter fullscreen mode Exit fullscreen mode

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])


Enter fullscreen mode Exit fullscreen mode

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>
  )
}


Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .