Theming Gatsby with gatsby-plugin-dark-mode
March 8, 2019
gatsby-plugin-dark-mode is a new plugin for Gatsby which handles some of the details of implementing a dark mode theme.
It provides:
- Browser code for toggling and persisting the theme.
- Automatic use of a dark mode theme (via the
prefers-color-scheme
CSS media query) if you’ve configured your system to use dark colour themes when available. - A React component for implementing theme toggling UI in your site.
First, install the plugin and configure Gatsby to use it:
npm install gatsby-plugin-dark-mode --save
// gatsby-config.js
module.exports = {
plugins: ['gatsby-plugin-dark-mode'],
}
Next, you need to provide a UI so users can toggle dark mode on and off.
The plugin module exports a ThemeToggler
component which takes a children
render prop, providing the current theme
name and a toggleTheme
function to change the theme.
Here’s an example of using ThemeToggler
with a checkbox to toggle the theme (as seen in the footer of this blog):
import React from 'react'
import { ThemeToggler } from 'gatsby-plugin-dark-mode'
class MyComponent extends React.Component {
render() {
return (
<ThemeToggler>
{({ theme, toggleTheme }) => (
<label>
<input
type="checkbox"
onChange={e => toggleTheme(e.target.checked ? 'dark' : 'light')}
checked={theme === 'dark'}
/>{' '}
Dark mode
</label>
)}
</ThemeToggler>
)
}
}
The toggled theme will be persisted across visits in localStorage.theme
.
Finally, you need to implement the theme itself.
The default theme names are 'light'
and 'dark'
- the plugin adds the current theme name to the <body>
element’s className
, so you can use global styles to implement theming.
A nice option is to use CSS variables like so:
/* global.css */
body {
--bg: white;
--textNormal: #222;
--textTitle: #222;
--textLink: blue;
--hr: hsla(0, 0%, 0%, 0.2);
background-color: var(--bg);
}
body.dark {
-webkit-font-smoothing: antialiased;
--bg: darkslategray;
--textNormal: rgba(255, 255, 255, 0.88);
--textTitle: white;
--textLink: yellow;
--hr: hsla(0, 0%, 100%, 0.2);
}
You can then use these variables in your site’s components…
class Layout extends React.Component {
render() {
return (
<div
style={{
backgroundColor: 'var(--bg)',
color: 'var(--textNormal)',
transition: 'color 0.2s ease-out, background 0.2s ease-out',
}}
>
...
</div>
)
}
}
…and in your Typography config if you’re using gatsby-plugin-typography
, which is included in the Gatsby Starter Blog:
// typography.js
import './global.css'
import Typography from 'typography'
import Wordpress2016 from 'typography-theme-wordpress-2016'
Wordpress2016.overrideThemeStyles = () => ({
a: {
color: 'var(--textLink)',
},
hr: {
background: 'var(--hr)',
},
// gatsby-remark-autolink-headers - use theme colours for the link icon
'a.anchor svg[aria-hidden="true"]': {
stroke: 'var(--textLink)',
},
})
Bonus Gatsby tip: customising HTML without html.js
If you want to customise the base HTML for your Gatsby app, an alternative to creating and tweaking your own copy of html.js
is to use the Server Side Rendering APIs by creating a gatsby-ssr.js
module.
For example, one of the things gatsby-plugin-dark-mode
handles is inserting a <script>
directly inside <body>
to set the initial theme, which avoids seeing a flash of the default light theme when revisiting the site after enabling dark mode.
This is implemented by using the onRenderBody
API’s setPreBodyComponents()
function to add a <script>
element to the list of components which will be rendered inside <body>
:
// gatsby-ssr.js
const React = require('react')
exports.onRenderBody = function({ setPreBodyComponents }) {
setPreBodyComponents([
React.createElement('script', {
dangerouslySetInnerHTML: {
__html: `
// <script> contents
`,
},
}),
])
}
Acknowledgements
The theme detecting/switching/persistence code and the suggested theming implementation are entirely from the implementation of overreacted.io by Dan Abramov - I’m just publishing them as a plugin to make them easier to use in my own blog, and for reuse by others.

A blog about programming, web dev and whatnot by Jonny Buchanan.