• Home
  • Blog
    • Programming
    • Art
    • Recipes
    • Home Automation
    • Life
    • Friday Faves
    • Books
    • Writing
    • Games
    • Web Accessibility
    • Advent of Code
  • Projects
  • GLSL Shader Art
  • Glitch Art

  • Github
  • Bluesky
  • Mastodon
  • Duolingo
  • LinkedIn
  • RSS
  • Bridgy Fed

mary.codes

Web development, art, and tinkering with fun projects. JavaScript, Python, WebGL, GLSL, Rust, Lua, and whatever else I find interesting at the moment.

Quick and dirty React.js and CSS dark mode

Mary KnizeBlog iconNov 26th, 2021
2 min read

Programming

The only thing in my life that doesn't have a dark mode is my own website.

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!


Other Programming posts

How I'm organizing my work in 2024

Finding the Design Engineer within the Full-Stack Developer

Displaying the current Git branch in my Linux terminal prompt

Generating a 3D map with OpenStreetMap and A-Frame

Translating OpenStreetMap data to HTML5 Canvas with Rust and WebAssembly

Latest posts

I started painting again

100 days of Japanese on Duolingo

How I'm organizing my work in 2024

Watch the documentary 'Good Night Oppy' if you're feeling science-y

Sculpted robo-goldfish, a rainbow office building, and a look inside click farms