Dark Mode for Actual Users

4 minutes to read

Looking at some documentation for a newer media query ‌prefers-color-scheme. This media query will detect whether the user's system is in a light or dark mode. That's pretty cool, it seems like every 10 minutes I read about another application having a dark mode and now a native way to detect that is coming to browsers. I'm on board. The browser support is a little sparse. It works in current versions of Safari, Firefox and it's coming to Chrome 77, with no signals from Edge or Opera.

Example

But how does this new media query work? Imagine combining the ability to detect the user's system theme with CSS's custom properties:

:root {
  --dark: #333;
  --light: #f7f7f7;
}

@media (prefers-color-scheme: light) {
  :root {
    --background: var(--light);
    --color: var(--dark);
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: var(--dark);
    --color: var(--light);
  }
}

html {
  background: var(--background);
  color: var(--color);
}

See a demo here.

That's pretty cool, 23 lines of CSS to dictate the style of your application. Which in the past you'd had to write with JavaScript to manage and it feels like it belongs in the CSS.

There is a catch here. If you use this simple implementation verbatim, the user has to switch their system UI to get your site to abide by their specific preferences.

Why Dark Mode?

Before you start implementing a dark mode with this pattern, let's look at dark mode as a concept. I'm an Android person and according to Google (as reported by the Verge), dark mode in their core applications like Google Keep, Google Calendar and Messages and on my system UI is better for my battery life. There's a visual choice too, there have been varying reports that seem to center around the idea that a dark mode doesn't hook your brain into your device like a light version of the same UI. Also Twitter and Facebook both have or are going to pivot to having this type of theme switcher in their applications. Not really an iOS person but iOS 13 is going to have a dark mode similar to Android.

Those are some reasons; good or bad or indifferent, they are reasons.

Ok, but Why not?

In this post they make a good case that the high contrast dark modes can out a strain on your eyesight and stark contrasts suffer legibility issues.

Regardless of what you decide to do with this and as it pertains to the web, this feature has some real user implications. As you make design considerations about adding a dark mode to your application or site please remember one thing above all else:

Please let your users opt out of their system default.

Your user knows what's best for their needs, empower them to make the right choice for them.

Middle Path

So there's a middle path to be forged here. One pattern all the native applications with dark modes have, is having a switch to toggle the theme. So here's an idea: why can't we accept the users default and let them opt out if they want to? Good idea, Charles, I'm glad you said something.

It's not unreasonable to assume that if the user set their system to a dark theme, that they want dark themed applications. Native applications follow this pattern but how do we do that with web technology?

Let's start by moving some of that media query logic from the CSS back to the JavaScript (but we're going to use less of it) and just use a class that holds those variables:

:root {
  --background: var(--light);
  --color: var(--dark);
}

.DarkMode {
  --background: var(--dark);
  --color: var(--light);
}
// We're assuming there's some checkbox element and a root element in the DOM
function checkSystem() {
  const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;

  if (prefersDark) {
    root.classList.add("DarkMode");
  } else {
    root.classList.remove("DarkMode");
  }
}

function initDarkMode() {
  checkSystem();

  if (root.classList.contains("DarkMode")) {
    checkbox.setAttribute("checked", true);
  }

  checkbox.addEventListener("change", event => {
    root.classList.toggle("DarkMode");
    const isDarkMode = root.classList.contains("DarkMode");
    checkbox.setAttribute("checked", isDarkMode);
  });
}

See that same from before but with this approach here.

Here we're checking whether that media query returns true or not, then adding a listen to the checkbox to toggle the dark mode on or off. We could do tons of more like store the theme choice in local storage or in a cookie where we could check and load that theme. If you're looking to progressively enhance this feature, more browsers support custom properties than this media query anyway.

Dark mode is great, I love this media query; it's super powerful and when you combine it with custom properties it's even more powerful. I love dark mode personally, but you need to let that user make that choice for themselves and the web is getting to a place where empowering to users to do that is much simpler than it had been.

More Reading