Skip to main content

All hooks

useDarkMode

Custom hook that returns the current state of the dark mode.


It uses internally useLocalStorage() to persist the value and listens the OS color scheme preferences.

Note: If you use this hook in an SSR context, set the initializeWithValue option to false.

Usage

import { useDarkMode } from './useDarkMode'

export default function Component() {
  const { isDarkMode, toggle, enable, disable } = useDarkMode()

  return (
    <div>
      <p>Current theme: {isDarkMode ? 'dark' : 'light'}</p>
      <button onClick={toggle}>Toggle</button>
      <button onClick={enable}>Enable</button>
      <button onClick={disable}>Disable</button>
    </div>
  )
}

API

function useDarkMode(options?: DarkModeOptions): DarkModeReturn

Custom hook that returns the current state of the dark mode.

Parameters

NameTypeDefault valueDescription
options?DarkModeOptions{}The initial value of the dark mode, default false.

Returns

An object containing the dark mode's state and its controllers.

Type declaration

DarkModeOptions

The hook options.

NameTypeDescription
defaultValuebooleanThe initial value of the dark mode.
initializeWithValuebooleanIf true (default), the hook will initialize reading localStorage. In SSR, you should set it to false, returning the defaultValue or false initially.
localStorageKeystringThe key to use in the local storage.

DarkModeReturn

The hook return type.

NameTypeDescription
disable() => voidFunction to disable the dark mode.
enable() => voidFunction to enable the dark mode.
isDarkModebooleanThe current state of the dark mode.
set(value: boolean) => voidFunction to set a specific value to the dark mode.
toggle() => voidFunction to toggle the dark mode.

Hook

import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect'
import { useLocalStorage } from '../useLocalStorage'
import { useMediaQuery } from '../useMediaQuery'
import { usePrevious } from '../usePrevious'

const COLOR_SCHEME_QUERY = '(prefers-color-scheme: dark)'
const LOCAL_STORAGE_KEY = 'usehooks-ts-dark-mode'

/** The hook options. */
export type DarkModeOptions = {
  /**
   * The initial value of the dark mode.
   * @default false
   */
  defaultValue?: boolean
  /**
   * The key to use in the local storage.
   * @default 'usehooks-ts-dark-mode'
   */
  localStorageKey?: string
  /**
   * If `true` (default), the hook will initialize reading `localStorage`.
   * In SSR, you should set it to `false`, returning the `defaultValue` or `false` initially.
   * @default true
   */
  initializeWithValue?: boolean
}

/** The hook return type. */
export type DarkModeReturn = {
  /** The current state of the dark mode. */
  isDarkMode: boolean
  /** Function to toggle the dark mode. */
  toggle: () => void
  /** Function to enable the dark mode. */
  enable: () => void
  /** Function to disable the dark mode. */
  disable: () => void
  /** Function to set a specific value to the dark mode. */
  set: (value: boolean) => void
}

/**
 * Custom hook that returns the current state of the dark mode.
 * @param {?DarkModeOptions} [options] - The initial value of the dark mode, default `false`.
 * @returns {DarkModeReturn} An object containing the dark mode's state and its controllers.
 * @public
 * @see [Documentation](https://usehooks-ts.com/react-hook/use-dark-mode)
 * @example
 * ```tsx
 * const { isDarkMode, toggle, enable, disable, set } = useDarkMode({ defaultValue: true });
 * ```
 */
export function useDarkMode(options: DarkModeOptions = {}): DarkModeReturn {
  const { defaultValue, localStorageKey = LOCAL_STORAGE_KEY, initializeWithValue = true } = options

  const isDarkOS = useMediaQuery(COLOR_SCHEME_QUERY, {
    initializeWithValue,
    defaultValue,
  })
  const [isDarkMode, setDarkMode] = useLocalStorage<boolean>(
    localStorageKey,
    defaultValue ?? isDarkOS ?? false,
    { initializeWithValue },
  )

  const prevIsDarkOS = usePrevious(isDarkOS)

  // Only sync when OS preference actually changes, not on initial mount
  useIsomorphicLayoutEffect(() => {
    if (prevIsDarkOS !== undefined && isDarkOS !== prevIsDarkOS) {
      setDarkMode(isDarkOS)
    }
  }, [isDarkOS, prevIsDarkOS, setDarkMode])

  return {
    isDarkMode,
    toggle: () => {
      setDarkMode(prev => !prev)
    },
    enable: () => {
      setDarkMode(true)
    },
    disable: () => {
      setDarkMode(false)
    },
    set: value => {
      setDarkMode(value)
    },
  }
}