How to Implement "dark mode" with Gatsby & React Hooks

Kim Hart - Aug 1 '19 - - Dev Community

I recently launched my new portfolio site and I have to say, I'm super proud! My old site was built on Wix years before I learned how to code and was in need of a major design update.

I landed on Gatsby for my setup and Netlify for my deployment platform and guys, I cannot recommend each of them enough. But this post isn't about that!

Why dark mode?

Turns out, a lot of people like dark-themed internet things (just ask Twitter). I chose to implement toggleable sunrise and sunset themes on my portfolio because it adds a level of interactivity to my otherwise static site, allowed me to play with more complex CSS, and it lets users customize their experience. It even persists through sessions via localStorage!

What'd I use?

I considered building this myself until I found this tool called use-dark-mode. In short, it's a custom React Hook that handles the storage part for you. Their docs are pretty great, but I'll walk you through my use case as well.

Implementation

  • You must use react@16.8.0 or greater which includes Hooks
  • This only works in functional components, so if you're using older React class components with non-hook lifecycle methods, you may have a hard time.

1. Install

You'll install both use-dark-mode and its Gatsby-specific plugin that helps with overall rendering of your themes:

yarn add use-dark-mode gatsby-plugin-use-dark-mode
Enter fullscreen mode Exit fullscreen mode

2. Add to Gatsby config

To prevent a flash of default-styled content on page load, add this block to your gatsby-config.js file. (More in the docs)

{
  resolve: "gatsby-plugin-use-dark-mode",
  options: {
     classNameDark: "dark-mode",
     classNameLight: "light-mode",
     storageKey: "darkMode",
     minify: true,
  },
}
Enter fullscreen mode Exit fullscreen mode

Note: you can name these classes whatever you like! I stuck with the defaults.

3. Add to React

Here's a ultra-simplified version of my hero component. It contains two icon components (sunrise and sunset) that fire handleTheme on click, which launch either darkMode.enable() or darkMode.disable() depending on their props.

The goal here is to change to dark mode when you click sunset, and light mode when you click sunrise.


import React from "react"
import useDarkMode from "use-dark-mode"
import Sunrise from "../components/icons/sunrise"
import Sunset from "../components/icons/sunset"

const Hero = () => {
  // Instantiate with the default behavior, in this case, it defaults to light-mode
 // This places "light-mode" class on document.body, as outlined in my gatsby-config.js
  const darkMode = useDarkMode(false);

  // Custom function that handles the toggling
  // When called, it replaces the class on document.body and holds it in localStorage
  const handleTheme = theme => theme === "dark" ? darkMode.enable() : darkMode.disable();

  return (
    <div className="hero">
      <Sunrise onClick={handleTheme} />
      <Sunset onClick={handleTheme} />
    </div>
  )
}

export default Hero;

Enter fullscreen mode Exit fullscreen mode

The sunset and sunrise icon components are super similar, they just pass different values ("light" and "dark"). Here's a slimmed-down version of Sunset:

import React from "react"

const Sunset = (props) => {
  // If the `onClick` prop exists, call it with 'dark'
  const handleClick = () => props.onClick && props.onClick('dark');

  return (
    <div className="theme-toggle" onClick={handleClick}>...</div>
  )
}
Enter fullscreen mode Exit fullscreen mode
  • Note: you could also accomplish this with passing boolean values (i.e. "true" for dark, but I chose to keep it more readable and used strings)

CSS

Now that we have the class on document.body toggling between light-mode and dark-mode when we click the sunrise or sunset icons, we can adjust our CSS to reflect the changes.

I used Less, which makes it super easy to apply rules based on parent values. Again, this is simplified, but hopefully you get the idea.

The .dark-mode & selector will look for anytime the dark-mode class exists on a higher component (in this case, the body tag). You can then apply whatever rules you need — in this case it's a variable for the background colors.

   .hero {
      background: @sunrise-gradient;
      .dark-mode & {
        background: @sunset-gradient;
      }
    }
Enter fullscreen mode Exit fullscreen mode

... and that's it!

Conclusion

You don't have to completely recreate the wheel to implement dark mode in a Gatsby app. Hopefully this was helpful and I'm happy to answer any questions in the comments!

. . . .
Terabox Video Player