Home
/ blog

A lazy dark mode

Yes. Dark mode. Dark theme. Night theme. Whatever you want to call it, the dark theme craze has been going strong for quite a while. It is now almost a must have for any new app out there.

But why?

Well I have always been an edge lord and remember loading custom explorer dark themes in windows XP so I can see why people like it. However, the real reason that IMO it should be a best practice is because of battery life. Dark theme has less light which means less battery. Cool cool.

But how?

Honestly, I have never gotten all the hype around some app releasing a dark mode. I know I have read Material Design specs on it and there are some rules about font colour depending on how dark the background is. However, I have found a much simpler method.

body {
  background: #333;
  color: white;
}

Yup. Seriously. The biggest aspect of a dark theme is a nice grey background and lighter text. I actually wouldn't do pure white as that can be too much contrast. Now since I am using rebass and Nextjs I really should use the theme support but I am going to show you the super nifty lazy mans way of doing it which you can see working currently.

Now this is assuming you are using Nestjs and rebass but it can be translated rather easily to other frameworks. I highly recommend looking at rebass. I like it bunches but something like Bootstrap would also work.

The idea

I want an api like this

import DarkModeToggle from 'components/DarkModeToggle'

...mark up
<DarkModeToggle />

...other markup

Than, I can just drop that guy in any page and be done with it.

Nextjs makes this nice as we can create a component with a global style on it that will update the body tag when in dark mode. If we don't render the component, the global styling won't be included so it will work as a drop in.

We want this dark mode to automatically turn to dark or light depending on the time of day.

Another thing we want which the current dark mode toggle I have doesn't do is save the selection locally and load it. This would mean that if there is a saved option, we override the above time of day feature.

The base I have currently looks like so.

import React, { useState } from "react";
import { Box } from "rebass";
import { FaRegLightbulb } from "react-icons/fa";

export default () => {
  const currentHours = new Date().getHours();
  const [isDarkMode, setDarkMode] = useState(
    currentHours >= 18 || currentHours < 7
  );

  return (
    <Box
      sx={{
        borderRadius: "100%",
        boxShadow:
          "0 11px 40px 0 rgba(0, 0, 0, 0.25), 0 2px 10px 0 rgba(0, 0, 0, 0.12)",
        position: "fixed",
        zIndex: "20",
        left: "0.5rem",
        bottom: "0.5rem",
        padding: "0.5rem",
        display: "flex",
        width: "2rem",
        height: "2rem",
        backgroundColor: "#444",
        color: isDarkMode ? "#ee7" : "#fff",
      }}
      onClick={() => setDarkMode(!isDarkMode)}
    >
      <FaRegLightbulb></FaRegLightbulb>
      <style jsx global>{`
        body {
          transition: 350ms linear color, 350ms linear background-color;
          ${isDarkMode
            ? `
            background-color: #222;
            color: #eee;
          `
            : ""}
        }
      `}</style>
    </Box>
  );
};

Most of this shouldn't be too hard to read. Display an icon in a button with some positioning. I went with a fixed position so I literally could drop it in at any level on a page instead of having to make sure I am a child of a container or something.

Now to add a user selection option we are simply going to have a useEffect that will check localStorage for some key and set that if found. Than we can update this if the user toggles. Nice. What does this look like?

import React, { useState, useEffect } from "react";
import { Box } from "rebass";
import { FaRegLightbulb } from "react-icons/fa";

const darkModeKey = "darkmode_key";

export default () => {
  const [isDarkMode, setDarkMode] = useState(false);

  useEffect(() => {
    const currentHours = new Date().getHours();
    const userSelection = window?.localStorage[darkModeKey];
    const timeOfDayDefault = currentHours >= 18 || currentHours < 7;
    const defaultDarkMode =
      userSelection !== undefined
        ? JSON.parse(userSelection)
        : timeOfDayDefault;
    setDarkMode(defaultDarkMode);
  }, []);

  return (
    <Box
      sx={{
        borderRadius: "100%",
        boxShadow:
          "0 11px 40px 0 rgba(0, 0, 0, 0.25), 0 2px 10px 0 rgba(0, 0, 0, 0.12)",
        position: "fixed",
        zIndex: "20",
        left: "0.5rem",
        bottom: "0.5rem",
        padding: "0.5rem",
        display: "flex",
        width: "2rem",
        height: "2rem",
        backgroundColor: "#444",
        color: isDarkMode ? "#ee7" : "#fff",
      }}
      onClick={() => (
        (localStorage[darkModeKey] = !isDarkMode), setDarkMode(!isDarkMode)
      )}
    >
      <FaRegLightbulb></FaRegLightbulb>
      <style jsx global>{`
        body {
          transition: 350ms linear color, 350ms linear background-color;
          ${isDarkMode
            ? `
            background-color: #222;
            color: #eee;
          `
            : ""}
        }
      `}</style>
    </Box>
  );
};

One thing to note is that since we are using SSR with Nextjs we have to put anything that relies on the window existing in a useEffect as that will not be run on the server render.

This is rather simple. Just add a call to update the localStorage and than load the default in a useEffect hook.

const currentHours = new Date().getHours();
const userSelection = window?.localStorage[darkModeKey];
const timeOfDayDefault = currentHours >= 18 || currentHours < 7;
const defaultDarkMode =
  userSelection !== undefined ? JSON.parse(userSelection) : timeOfDayDefault;
setDarkMode(defaultDarkMode);

This bit is a little more complicated.

  1. We get user selection if any
  2. We get time of day default
  3. If user selection is undefined, (nothing found) we return time of day default
  4. If user selection is found, we need to parse it as the boolean will be stored as a string.

The even lazier solution

As I was writing this I wanted to make a useLocalStorage hook and looked at some other examples and came across usehooks. This has some awesome hooks and one of them is...yup a useDarkMode hook. What more, is that their version will also look at the devices preference via a media query. Super dope. It also saves any updates to localStorage like ours. Even more dope.

There are also many more awesome examples of hooks and how to use them better.

Thanks for reading.