useDarkMode

dark mode with auto data persistence.

useDarkMode provides a simple dark/light mode toggle with automatic persistence to localStorage. It returns a tuple of the current theme state and a toggle function. The hook applies CSS class names (configurable as classNameDark and classNameLight) to the <html> element and syncs with the user’s system preference via the prefers-color-scheme media query when no stored value exists. It respects the system preference as a default but allows the user to override it.

When to Use

  • Adding a dark mode toggle to your application with automatic persistence
  • Building a theme switcher that respects the user’s system color scheme preference as a default
  • Applying dark/light CSS classes to the root element for CSS-driven theming

Notes

  • SSR-safe: Returns false for the dark mode state during server-side rendering. No DOM or localStorage access occurs on the server. See the Tips section below for preventing flash of unstyled content.
  • Persistence: Stores the preference in localStorage under the key "reactuses-color-scheme". The stored value is automatically migrated from the old boolean format to the new string format in v6.1.0+.
  • Related hooks: For multi-theme support beyond dark/light, see useColorMode. For detecting the system preference without persistence, see usePreferredDark or usePreferredColorScheme.

Breaking Change in v6.1.0

Storage format has changed from boolean to string values. If you’re upgrading from a previous version:

  • Old format: true/false boolean values
  • New format: "dark"/"light" string values
  • Auto-migration: Existing data will be automatically migrated
  • SSR scripts: Need to be updated to handle string comparisons
Migration Guide

If you have custom SSR scripts, update them as shown in the examples below. The library handles data migration automatically, but your custom scripts need to handle both old and new formats during the transition period.

Tips

click to open

For server-side rendered applications, since it’s not possible to obtain the user’s color preference on the server side, there might be a flicker during the first render. To avoid this issue, you can refer to the following steps.

  1. add a script before your content.
<script
  dangerouslySetInnerHTML={{
    __html: `
          (function () {
            function setDark(dark) {
              dark &&  document.documentElement.classList.add('dark');
            }
            let store = localStorage.getItem('reactuses-color-scheme');
            let dark;
            if(store === null){
              const darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
              dark = darkQuery.matches;
            }else {
              if (store === 'true' || store === 'false') {
                const boolValue = store === 'true';
                const stringValue = boolValue ? 'dark' : 'light';
                localStorage.setItem('reactuses-color-scheme', stringValue);
                dark = boolValue;
              } else {
                dark = store === 'dark';
              }
            }
            setDark(dark)
          })();
      `,
  }}
></script>
  1. To conveniently manage theme colors in a unified way, we recommend using context to store them.
import { useDarkMode } from "@reactuses/core";
import React, { createContext, useContext } from "react";

type ThemeContext = { theme: boolean; toggleTheme: () => void };

const ThemeContext = createContext<ThemeContext | undefined>(undefined);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [dark, toggle] = useDarkMode({
    classNameDark: "dark",
    classNameLight: "light",
    defaultValue: false,
  });

  return (
    <ThemeContext.Provider value={{ theme: !!dark, toggleTheme: toggle }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (context === undefined) {
    throw new Error("useTheme must be used within a ThemeProvider");
  }
  return context;
}

Usage

Live Editor
function Demo() {
  const [theme, toggleDark] = useDarkMode({
    classNameDark: "dark",
    classNameLight: "light",
    defaultValue: false,
  });

  return (
    <div>
      <div>theme: {theme ? "dark" : "light"}</div>
      <br />
      <div>
        <button onClick={toggleDark}>toggleDark</button>
      </div>
    </div>
  );
}
Result

API

UseDarkOptions

PropertyDescriptionTypeDefaultValue
selectorCSS Selector for the target element applying tostring'html'
attributeHTML attribute applying the target elementstring'class'
defaultValuedefault valuebooleanfalse
storageKeyKey to persist the data into localStorage/sessionStorage.string'reactuses-color-scheme'
storageStorage object, can be localStorage or sessionStorage() => StoragelocalStorage
classNameDarkname dark apply to elementstring (Required)-
classNameLightname light apply to elementstring (Required)-

useDarkMode

Returns

readonly [boolean | null, () => void, React.Dispatch<React.SetStateAction<boolean | null>>]: A tuple with the following elements:

  • The current value of the dark state.
  • A function to toggle the dark state.
  • A function to update the dark state.

Arguments

ArgumentDescriptionTypeDefaultValue
options-UseDarkOptions (Required)-