Functional BytesFunctional Bytes

Using Users' Preferences for Dark Mode


Dark mode is available and used pretty much everywhere now. Most operating systems allow users to set their preference between light and dark mode and this can be detected in the browser.

The following are notes on how to detect users' preferences in CSS and JavaScript / TypeScript.

CSS Media Query

For CSS, all that is required is to write your styles by default in light mode and then wrap your dark mode changes in the @media (prefers-color-scheme: dark) media query (Note: you can also write your styles for dark mode by default and change the query to match light instead to do the inverse).

/** Default for light mode */
body {
  background-color: #1f1f1f;
  color: #efefef;
}

@media (prefers-color-scheme: dark) {
  /** Make background dark and text light in dark mode */
  body {
    background-color: #1f1f1f;
    color: #efefef;
  }
}

JavaScript - One time check

if (
  window.matchMedia &&
  window.matchMedia('(prefers-color-scheme: dark)').matches
) {
  // Dark mode
} else {
  // Light mode
}

JavaScript / TypeScript - Watch for color scheme preference change

window.matchMedia &&
window
  .matchMedia('(prefers-color-scheme: dark)')
  .addEventListener('change', (evt: MediaQueryListEvent) => {
    // This event fires whenever the client changes their preferred color scheme
    if (evt.matches) {
      // Dark Mode
    } else {
      // Light mode
    }
  });

React Hook

Example on CodeSandbox

File: usePrefersDarkMode.ts

import { useEffect, useState } from "react";

export default function usePrefersDarkMode() {
  const [prefersDarkMode, setPrefersDarkMode] = useState<boolean>(
    window.matchMedia("(prefers-color-scheme: dark)").matches
  );

  useEffect(() => {
    const onPreferenceChange = (evt: MediaQueryListEvent) => {
      setPrefersDarkMode(evt.matches);
    };
    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    mediaQuery.addEventListener("change", onPreferenceChange);

    return () => {
      // Remove listener on unmount
      mediaQuery.removeEventListener("change", onPreferenceChange);
    };
  }, []);

  return prefersDarkMode;
}

References