By ReactUse Team

React Dark Mode Toggle: Complete Guide

Dark mode has become a standard feature that users expect in modern web applications. A dark mode toggle lets users switch between light and dark color schemes, reducing eye strain in low-light environments and saving battery on OLED displays. This guide walks you through implementing dark mode in React, from manual CSS approaches to a production-ready solution with the useDarkMode hook.

Why Dark Mode Matters

Dark mode is no longer a nice-to-have โ€” itโ€™s a user expectation. Studies show that over 80% of users prefer dark mode in at least some contexts. Beyond user preference, dark mode provides real benefits:

  • Reduced eye strain in low-light conditions
  • Lower battery consumption on OLED and AMOLED screens
  • Improved accessibility for users with light sensitivity
  • A more polished product feel that signals attention to detail

Getting it right involves more than swapping background colors. You need to handle system preferences, persist the userโ€™s choice, and avoid flash-of-wrong-theme on page load.

The Manual CSS Approach

The simplest starting point is a CSS class on the root element:

:root {
  --bg-color: #ffffff;
  --text-color: #1a1a1a;
}

html.dark {
  --bg-color: #1a1a1a;
  --text-color: #e0e0e0;
}

body {
  background-color: var(--bg-color);
  color: var(--text-color);
}

Then toggle the class in React:

import { useState } from "react";

function ThemeToggle() {
  const [isDark, setIsDark] = useState(false);

  const toggle = () => {
    document.documentElement.classList.toggle("dark");
    setIsDark((prev) => !prev);
  };

  return <button onClick={toggle}>{isDark ? "Light Mode" : "Dark Mode"}</button>;
}

This works for basic cases, but it has problems. The preference resets on page reload, it ignores the userโ€™s system setting, and the logic gets duplicated across components.

Detecting System Preferences

Most operating systems let users set a system-wide dark mode preference. You can detect it with the prefers-color-scheme media query:

import { useEffect, useState } from "react";

function useSystemDarkMode() {
  const [isDark, setIsDark] = useState(
    () => window.matchMedia("(prefers-color-scheme: dark)").matches
  );

  useEffect(() => {
    const mql = window.matchMedia("(prefers-color-scheme: dark)");
    const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
    mql.addEventListener("change", handler);
    return () => mql.removeEventListener("change", handler);
  }, []);

  return isDark;
}

This respects the userโ€™s OS setting and reacts to changes in real time. But you still need to handle localStorage persistence, SSR safety, applying classes to the DOM, and keeping everything in sync. Thatโ€™s a lot of boilerplate for every project.

The Easy Way: useDarkMode

The useDarkMode hook from ReactUse handles all of this in a single call. It detects system preferences, persists the userโ€™s choice to localStorage, applies CSS classes to the DOM, and works safely with SSR:

import { useDarkMode } from "@reactuses/core";

function ThemeToggle() {
  const [isDark, toggle] = useDarkMode({
    classNameDark: "dark",
    classNameLight: "light",
  });

  return (
    <button onClick={toggle}>
      {isDark ? "Switch to Light" : "Switch to Dark"}
    </button>
  );
}

The hook returns a tuple of three values:

  1. isDark โ€” a boolean indicating whether dark mode is active
  2. toggle โ€” a function to switch between dark and light mode
  3. setDark โ€” a setter function for programmatic control

Persisting User Preference

By default, useDarkMode stores the userโ€™s choice in localStorage under the key reactuses-color-scheme. You can customize both the key and the storage backend:

const [isDark, toggle] = useDarkMode({
  classNameDark: "dark",
  classNameLight: "light",
  storageKey: "my-app-theme",
});

If you need sessionStorage instead of localStorage:

const [isDark, toggle] = useDarkMode({
  classNameDark: "dark",
  classNameLight: "light",
  storage: () => sessionStorage,
});

When no stored preference exists, the hook automatically falls back to the userโ€™s system preference via prefers-color-scheme.

Common Patterns

Theme-Aware Component

Build components that adapt their styling based on the current theme:

import { useDarkMode } from "@reactuses/core";

function Card({ children }: { children: React.ReactNode }) {
  const [isDark] = useDarkMode({
    classNameDark: "dark",
    classNameLight: "light",
  });

  return (
    <div style={{
      background: isDark ? "#2d2d2d" : "#ffffff",
      color: isDark ? "#e0e0e0" : "#1a1a1a",
      padding: "1.5rem",
      borderRadius: "8px",
    }}>
      {children}
    </div>
  );
}

Applying to a Custom Selector

By default, classes are applied to the <html> element. You can target a different element:

const [isDark, toggle] = useDarkMode({
  selector: "#app-root",
  attribute: "data-theme",
  classNameDark: "dark",
  classNameLight: "light",
});

This adds the class to the element matching #app-root instead, and you can use a data attribute rather than a class if your CSS framework expects it.

Installation

npm i @reactuses/core

Or with other package managers:

pnpm add @reactuses/core
yarn add @reactuses/core

ReactUse provides 100+ hooks for React. Explore them all โ†’