🌙 How I set Dark Mode for Gatsby website
Tags: webdev, react, gatsby, javascript
Published: 2019-11-27 14:47:25

I recently decided to add dark and light mode to my website so that website visitors can easily switch to an eye-friendly design whenever they want.

![](https://media.giphy.com/media/Svce0tyqu9E0vN2d2T/source.gif)

## Why dark mode?

Dark and light mode can provide user-friendly experience on website. I choose to implement toggleable dark mode (reference to neon 80's theme) and light mode (classic style wtih accent colors) and , in the same time, it adds a some level interaction to my website.

## What I used?

I found out there is a special plugin in Gatsby plugin library [gatsby-plugin-dark-mode](https://www.gatsbyjs.org/packages/gatsby-plugin-dark-mode/?=dark) but I decided not to touch ready-to-use solution but to dive deep to custom one.

In order to implement dark-light mode I chose to stay with [SSR](https://www.gatsbyjs.org/docs/ssr-apis/) and React Hooks as [useEffect](https://reactjs.org/docs/hooks-effect.html) and [useState](https://reactjs.org/docs/hooks-state.html).

## Implementation

1. First of all I decided to add theme item and its value to `localStorage`. I used `gatsby-ssr.js` to set preBodyComponent in order to have script uploaded as soon as possible.

```js
const React = require('react')

exports.onRenderBody = ({ setPreBodyComponents }) => {
  setPreBodyComponents([
    React.createElement('script', {
      dangerouslySetInnerHTML: {
        __html: `
          (() => {    
            window.__onThemeChange = function() {};                
            function setTheme(newTheme) {                  
              window.__theme = newTheme;                  
              preferredTheme = newTheme;                  
              document.body.className = newTheme;                 
              window.__onThemeChange(newTheme);                
            }

            let preferredTheme
            try {
              preferredTheme = localStorage.getItem('theme')
            } catch (err) {}

            window.__setPreferredTheme = newTheme => {
              setTheme(newTheme)
              try {
                localStorage.setItem('theme', newTheme)
              } catch (err) {}
            }

            let darkQuery = window.matchMedia('(prefers-color-scheme: dark)')
            darkQuery.addListener(e => {
              window.__setPreferredTheme(e.matches ? 'light' : 'dark')
            })

            setTheme(preferredTheme || (darkQuery.matches ? 'light' : 'dark'))
          })()
        `,
      },
    }),
  ])
}
```

2. After that I went to Header component and added our useEffect and useState hooks.

> _What does useEffect do?_
>
> By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates.

```js
useEffect(() => {
  setTheme(window.__theme)
  window.__onThemeChange = () => {
    setTheme(window.__theme)
  }
}, [])
```

Then I needed to add useState hook to trigger state change every time I want to switch theme.

There is a big **BUT** here. I faced up to using `null` in useState hook that caused rendering Header twice every time clicking on theme toggler. The solution is to provide an initial state to prevent double render.

_Here will be a screenshot_

```js
const [theme, setTheme] = useState(websiteTheme)
```

You can see that initial state of useState hook is `websiteTheme`. It holds a `window.__theme` value you can see in `gatsby-ssr.js`. And I added a condition for server side rendering because **THERE IS NO WINDOW** while Gatsby is building website.

> _Kyle Mathews states:_
>
> During development, react components are only run in the browser where window is defined. When building, Gatsby renders these components on the server where window is not defined.

```js
let websiteTheme
if (typeof window !== `undefined`) {
  websiteTheme = window.__theme
}
```

In the end I added a `ThemeToggle` function which toggles website theme between `dark` and `light` mode

```js
const ThemeToggle = () => {
  window.__setPreferredTheme(websiteTheme === 'dark' ? 'light' : 'dark')
}
```

and toggle button

```html
<button onClick="{ThemeToggle}">
  {theme === 'dark' ? (
  <img src="{sun}" alt="Light mode" />
  ) : (
  <img src="{moon}" alt="Dark mode" />
  )}
</button>
```

<hr />

Here is complete version of Header component:

```js
// src/components/Header.index.js

import React, { useState, useEffect } from 'react'
import sun from '../../images/sun.svg'
import moon from '../../images/moon.svg'

const Header = props => {
  let websiteTheme
  if (typeof window !== `undefined`) {
    websiteTheme = window.__theme
  }

  const [theme, setTheme] = useState(websiteTheme)

  useEffect(() => {
    setTheme(window.__theme)
    window.__onThemeChange = () => {
      setTheme(window.__theme)
    }
  }, [])

  const ThemeToggle = () => {
    window.__setPreferredTheme(websiteTheme === 'dark' ? 'light' : 'dark')
  }

  return (
    ...skipped...
        <button onClick={ThemeToggle}>
          {theme === 'dark' ? (
            <img src={sun} alt="Light mode" />
          ) : (
            <img src={moon} alt="Dark mode" />
          )}
        </button>
    ...skipped...
  )
}

export default Header
```

So we are almost done. The last thing we need to add is out styles for `dark` and `light` theme. I used `GlobalStyle` providing by `styled-components`. Don't worry I will provide solution with css as well. So, we need to create a GlobalStyle.js component in style folder. Inside GlobalStyle.js file we type this:

```js
// src/styles/GlobalStyle.js

import { createGlobalStyle } from 'styled-components'
export const GlobalStyle = createGlobalStyle`
  body {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    background-color: var(--bg);
    color: var(--textNormal);

    &.dark {
      --bg: #221133;
      --textNormal: #fff;
    }

    &.light {
      --bg: #fff;
      --textNormal: #000;
    }
  `
```

After I go to `Layout.js` component which is responsible for website layout and insert `GlobalStyle` into it.

```js
// src/layout/index.js

...skiped...
import { ThemeProvider } from 'styled-components'
import { GlobalStyle } from '../styles/GlobalStyle'

export default ({ children }) => {
  return (
    <ThemeProvider theme={styledTheme}>
      <GlobalStyle />
        <Header />
        {children}
        <Footer />
    </ThemeProvider>
  )
}
```

That's it! Every time you click on toggle button you will change theme between dark and light versions.

Thanks for reading and happy coding 😉 !

Useful links:

- [Dan Abramov's solution](https://github.com/gaearon/overreacted.io/blob/master/src/html.js)
- [Gatsby SSR APIs](https://www.gatsbyjs.org/docs/ssr-apis/)
- [Window is not defined](https://github.com/gatsbyjs/gatsby/issues/309)