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
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,
},
}
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;
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>
)
}
- 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;
}
}
... 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!
Top comments (7)
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()
anddarkMode.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.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....
Whoa, your site looks so good!! Love the design, the toggle between dark/light mode is super fun too.
Thanks Jess! π
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 ?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.
Hi I've been trying to toggle the states with just one button but it doesn't seem to work, Can you help? please