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:
isDark— a boolean indicating whether dark mode is activetoggle— a function to switch between dark and light modesetDark— 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
Related Hooks
- useDarkMode documentation — full API reference and interactive demo
- useColorMode — for multi-mode theming beyond light/dark
- useMediaQuery — for responding to any CSS media query
- useLocalStorage — for general-purpose persistent state
ReactUse provides 100+ hooks for React. Explore them all →