Jamena McInteer

Adding Dark Mode to a Gatsby site using Hooks and Styled Components

Los Angeles street at night
Photo by Andre Benz on Unsplash

In this post I will be showing you how I added Dark Mode to an existing Gatsby site that was already set up with Hooks and Styled Components. In addition to the theme styling changes, I also needed to style some SVG icons differently for dark mode. I used localStorage to save the user’s preference.

This post assumes that you already have a working Gatsby project using React Hooks and Styled Components. You may want to check out the following links if your site isn’t set up this way:

Setting Up the Themes

Originally, I stored my colors in a JSON directly in src/components/layout.js that was then passed into the Styled Components’ ThemeProvider. To implement dark mode, I created two files in a themes folder: dark.js and light.js, and moved my json into these files.

dark.js

export default {
  colors: {
    background: "#1f2330",
    primaryDark: "#e34f8c",
    primaryMedium: "#f6f0ff",
    primaryLight: "#373a50",
    secondaryDark: "#f1ebfa",
    secondaryMedium: "#f1ebfa",
    secondaryLight: "#f1ebfa",
    focusBorder: "#91BA8D",
    svgIcons: "#f1ebfa"
  }
}

light.js

export default {
  colors: {
    background: "#FFFFFF",
    primaryDark: "#AF5A65",
    primaryMedium: "#DEBDC2",
    primaryLight: "#E9DCDE",
    secondaryDark: "#5A5052",
    secondaryMedium: "#BBBBBB",
    secondaryLight: "#DCDCDC",
    focusBorder: "#91BA8D",
    svgIcons: "#B86B77"
  }
}

Setting Up Toggle Button

In my layout.js file, I set up a toggle button to switch the theme mode. Here I used useState and useEffect to keep track of the toggle state, and save and retrieve the theme mode from localStorage. Finally, I passed the appropriate theme into ThemeProvider.

layout.js

import React, { useState, useEffect } from "react"
import styled, { ThemeProvider } from "styled-components"
import Button from "./button"
import lightTheme from "../theme/light"
import darkTheme from "../themes/dark"
...
const Layout = ({ children }) => {
  const [isDarkMode, setIsDarkMode] = useState(false)
  useEffect(() => {
    setIsDarkMode(localStorage.getItem("isDarkMode") === "true" ? true : false)
  })

  const theme = isDarkMode ? darkTheme : lightTheme

  return (
    <ThemeProvider theme={theme}>
    ...
    <Button
      onClick={() => {
        setIsDarkMode(!isDarkMode)
        localStorage.setItem("isDarkMode", !isDarkMode)
      }}
      text={isDarkMode ? "Light Mode" : "Dark Mode"}
    />
    ...
  )
}
...

export default Layout

Working with SVGs

This setup worked fine, and I was able to access the theme color variables when styling components using:

${props => props.theme.colors.SOME_COLOR}

However, I ran into issues when trying to access props.theme.colors in my JSX so I could pass the colors into my SVG React components. The theme was simply not available to my Gatsby page components, only to my styled components.

I set up my SVGs as React components so I could pass the colors down as props. For example:

code.js

import React from "react"

const SVGIcon = ({ c1, c2, c3 }) => <svg width="75" height="75" viewBox="0 0 75 75" fill="none" xmlns="http://www.w3.org/2000/svg">...<path d="..." fill={c1}/>...</svg>

export default SVGIcon

To make the theme colors available to be passed down as props to my SVG React components, I used the useContext hook and the wrapPageElement Gatsby browser and ssr API.

I changed both gatsby-browser.js and gatsby-ssr.js to use wrapPageElement. Please note that if you do not update both files, this dark mode setup will fail in either the dev or the build version.

gatsby-browser.js / gatsby-ssr.js

const React = require("react")
const Layout = require("./src/components/layout").default

exports.wrapPageElement = ({ element, props }) => {
  return <Layout {...props}>{element}</Layout>
}

And then within the page components, <Layout> became <>. This allowed the theme prop to be available for useContext within the page.

I used the useContext hook with Styled Components’ ThemeContext in the page component that needed to pass the colors as props to the SVG components. The page I needed to update in this case was index.js:

index.js

import React, { useContext } from "react"
import styled, { ThemeContext } from "styled-components"
...
import Code from "../components/svg/code

const IndexPage = () => {
  const themeContext = useContext(ThemeContext)

  return (
    <>
      ...
      <Code c1={themeContext.colors.svgIcons} />
      ...
    </>
  )
}

export default IndexPage

Using ThemeContext, all the theme’s values are now available for use in the page components. Beyond colors, this setup would also allow for displaying different icons, images, fonts, and even page layouts, all with the power of Styled Components’ ThemeProvider and ThemeContext.

To see the full code, please visit this project’s GitHub repository.

Share

Think others might enjoy this post? Share it!