DEV Community

Cover image for How to Implement "dark mode" with Gatsby & React Hooks
Kim Hart
Kim Hart

Posted on

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

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.


  • 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} />

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)


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!


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!

Top comments (7)

httpjunkie profile image
Eric Bishard • Edited

Love this tutorial, implementing my own dark mode for a simple application, so this was useful for that as well. I like the idea of using darkMode.enable() and darkMode.disable(). But I also thought about using a mode variable and assigning it 'light' and 'dark'. This way it allows me to add something like 'high-contrast-light' and 'high-contrast-dark' (more options) and potentially will scale a bit better. But tracking by the boolean makes for a simple example. Thanks for this.

prashant1k99 profile image
Prashant Singh

First of all Great Portfolio site. It is really something to be proud of...
And thanks for the great piece of information. It really makes it simple to implement dark mode....

jess profile image
Jess Lee

Whoa, your site looks so good!! Love the design, the toggle between dark/light mode is super fun too.

kim_hart profile image
Kim Hart

Thanks Jess! πŸ™‚

popoo profile image

Hello. I want a dark mode by default, but when I use const darkMode = useDarkMode(true) it still keeps it to light mode by default. Any idea ?

kim_hart profile image
Kim Hart

Hard to know without seeing your code, my first guess is that your β€˜dark-mode’ and β€˜light-mode’ classes are applying the wrong CSS. Double check which classname is being applied to the body and which css properties are attached to those classes.

teecodes profile image

Hi I've been trying to toggle the states with just one button but it doesn't seem to work, Can you help? please