Quick and dirty React.js and CSS dark mode
I couldn't decide on a project this weekend, so I decided to create a fast dark mode toggle for my site here. It was something I knew that I could finish quickly, as long as I didn't try to get too fancy with it.
The first thing I had to do was add a new class to the CSS file that would set all backgrounds to dark grey and all text to a light lavender color. I'll be adding the .dark
class to the body of the page when dark mode is active.
.dark,
.dark main,
.dark #headerDiv *,
.dark h1,
.dark h2,
.dark h3,
.dark h4,
.dark h5,
.dark h6 {
background-color: #1d1d21;
color: lavender !important;
}
Then, I imported Font Awesome icons into my header.js
file and created an icon that will conditionally show a sun or a moon, depending on the state of the page, and added that to the row of navigation icons that I already have by calling sunMoon
in that nav component.
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faSun, faMoon} from "@fortawesome/free-solid-svg-icons";
const sunMoon = (
<span id="darkmode" role="button" tabIndex="0" onClick={changeMode}>
<FontAwesomeIcon
title={darkClass === 'dark' ? "Light mode" : "Dark mode"}
icon={darkClass === 'dark' ? faSun : faMoon}
width="16px"
height="16px"
/>
</span>
)
#darkmode {
margin: 0.3em 0.5em;
padding: 0.4em 0.5em;
cursor: pointer;
}
I add a new state hook to the top of the component. darkClass
will either be "dark" or "", depending on the state of the page.
const [darkClass, setDarkClass] = useState('');
I'm already using gatsby-plugin-react-helmet to add metadata to the head of the page. Helmet makes it easy to update the body tag with the new "dark" classname.
import Helmet from "react-helmet";
<Helmet>
<body className={darkClass}/>
</Helmet>
The onClick handler for the icon calls a function called changeMode
, so I need to write that function to actually set the class on click.
function changeMode() {
if (darkClass === 'dark') {
setDarkClass('');
} else {
setDarkClass('dark');
}
}
When I preview the page the code works, but if I switch pages, the site will revert back to the default light mode. I need to use localStorage to make the browser remember the user's preference from page to page, and even if the site is refreshed or visited later.
Gatsby's build stage doesn't recognize window
, so using localStorage without a check for window
will throw an error, even though it will work in production. I'll add localStorage, with the check, to my changeMode function.
function changeMode() {
if (darkClass === 'dark') {
setDarkClass('');
if (typeof window !== 'undefined' && window.localStorage) {
localStorage.removeItem('darkMode');
}
} else {
setDarkClass('dark');
if (typeof window !== 'undefined' && window.localStorage) {
localStorage.setItem('darkMode', 'dark');
}
}
}
I also need to add an effect hook that will check for the presence of window
and will set the darkClass
state to the localStorage setting.
useEffect(() => {
if (typeof window !== 'undefined' && window.localStorage) {
setDarkClass(localStorage.getItem('darkMode'));
}
}, []);
And that's it! That's all the code I needed to add a very quick and dirty dark mode button to my site!