diff --git a/src/hooks/index.ts b/src/hooks/index.ts
index 605e594a..0dd8e495 100644
--- a/src/hooks/index.ts
+++ b/src/hooks/index.ts
@@ -5,6 +5,7 @@ export * from './useBreakpoints/useBreakpoints';
export * from './useBrowserLanguage/useBrowserLanguage';
export * from './useClickOutside/useClickOutside';
export * from './useClipboard/useClipboard';
+export * from './useColorMode/useColorMode';
export * from './useCounter/useCounter';
export * from './useCssVar/useCssVar';
export * from './useDebounceCallback/useDebounceCallback';
diff --git a/src/hooks/useColorMode/useColorMode.demo.tsx b/src/hooks/useColorMode/useColorMode.demo.tsx
new file mode 100644
index 00000000..4533da70
--- /dev/null
+++ b/src/hooks/useColorMode/useColorMode.demo.tsx
@@ -0,0 +1,18 @@
+import { useColorMode } from './useColorMode';
+
+const Demo = () => {
+ const [mode, setMode] = useColorMode();
+
+ return (
+ <>
+
+ current mode: {mode}
+
+
+
+
+ >
+ );
+};
+
+export default Demo;
diff --git a/src/hooks/useColorMode/useColorMode.ts b/src/hooks/useColorMode/useColorMode.ts
new file mode 100644
index 00000000..e6c048ac
--- /dev/null
+++ b/src/hooks/useColorMode/useColorMode.ts
@@ -0,0 +1,151 @@
+import type { RefObject } from 'react';
+
+import { getElement } from '@/utils/helpers';
+
+import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect/useIsomorphicLayoutEffect';
+import { useMutationObserver } from '../useMutationObserver/useMutationObserver';
+import type { UseStorageInitialValue, UseStorageOptions } from '../useStorage/useStorage';
+import { useStorage } from '../useStorage/useStorage';
+
+export type BasicColorMode = 'light' | 'dark';
+export type BasicColorSchema = BasicColorMode | 'auto';
+
+const memoryStorageMap = new Map();
+const memoryStorage = {
+ getItem: (key: BasicColorSchema) => memoryStorageMap.get(key) ?? null,
+ setItem: (key: BasicColorSchema, value: string) => memoryStorageMap.set(key, value),
+ removeItem: (key: BasicColorSchema) => memoryStorageMap.delete(key),
+ length: memoryStorageMap.size,
+ key: () => null,
+ clear: () => memoryStorageMap.clear()
+};
+const CSS_DISABLE_TRANS = '*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}';
+
+export type UseColorModeTarget =
+ | RefObject
+ | (() => Element)
+ | Element;
+
+/** The use color mode return type */
+export type UseColorModeReturn = [
+ /** Current color mode */
+ string | undefined,
+ /** Function to set the color mode */
+ (value: T) => void
+];
+
+export interface UseColorModeOptions<
+ T extends string = BasicColorMode,
+ Target extends UseColorModeTarget = UseColorModeTarget
+>
+ extends UseStorageOptions {
+ /** Target element applying to */
+ target?: Target;
+ /** HTML attribute applying the target element */
+ attribute?: string;
+ /** The initial color mode */
+ initialValue?: UseStorageInitialValue;
+ /** Prefix when adding value to the attribute */
+ modes?: Partial>;
+ /** A custom handler for handle the updates. When specified, the default behavior will be overridden. */
+ storageKey?: string | null;
+ /** Storage object, can be localStorage or sessionStorage */
+ storage?: Storage;
+ /** Disable CSS transitions */
+ disableTransition?: boolean;
+}
+
+/**
+ * @name useColorMode
+ * @description - Hook that work with color mode
+ * @category Utilities
+ *
+ * @template T The color mode type
+ * @template Target The target element
+ * @param {Target} [options.target=document.documentElement] The target element applying to
+ * @param {string} [options.attribute='class'] HTML attribute applying the target element
+ * @param {string} [options.initialValue='auto'] The initial color mode
+ * @param {Record} [options.modes={light: 'light', dark: 'dark', auto: ''}] The color modes
+ * @param {string} [options.storageKey='reactuse-color-scheme'] Prefix when adding value to the attribute
+ * @param {Storage} [options.storage=localStorage] Storage object
+ * @param {boolean} [options.disableTransition=true] Disable CSS transitions
+ * @returns {UseColorModeReturn} An object containing the color mode
+ *
+ * @example
+ * const {mode, setMode} = useColorMode();
+ */
+export const useColorMode = <
+ T extends string = BasicColorMode,
+ Target extends UseColorModeTarget = UseColorModeTarget
+> (options?: UseColorModeOptions): UseColorModeReturn => {
+ const {
+ target = document.documentElement,
+ attribute = 'class',
+ initialValue = 'auto',
+ storageKey = 'reactuse-color-scheme',
+ storage,
+ modes = {},
+ disableTransition = true
+ } = options ?? {};
+
+ const possibleModes = {
+ auto: '',
+ light: 'light',
+ dark: 'dark',
+ ...(modes || {})
+ } as Record;
+
+ const calculatedStorage = !storage && storageKey === null ? memoryStorage : storage;
+ const { value: mode, set } = useStorage(storageKey ?? '', { initialValue, storage: calculatedStorage });
+
+ useMutationObserver(target, () => {
+ const element = getElement(target) as HTMLElement | null | undefined;
+ if (!element) {
+ return;
+ }
+
+ const attributeValue = element.getAttribute(attribute);
+
+ if (attributeValue && attributeValue in possibleModes) {
+ set(possibleModes[attributeValue as BasicColorMode | T]);
+ } else {
+ set('light');
+ }
+ }, {
+ attributes: true
+ });
+
+ const setMode = (value: BasicColorSchema | T) => {
+ if (value === 'auto') {
+ const media = window?.matchMedia('(prefers-color-scheme: dark)');
+ value = media?.matches ? 'dark' : 'light';
+ }
+
+ const element = getElement(target) as HTMLElement | null | undefined;
+
+ if (element) {
+ const attributeValue = possibleModes[value];
+ element.setAttribute(attribute, attributeValue);
+ }
+
+ set(possibleModes[value]);
+
+ if (disableTransition) {
+ const style = window.document.createElement('style');
+ style.appendChild(document.createTextNode(CSS_DISABLE_TRANS));
+ window.document.head.appendChild(style);
+ // Calling getComputedStyle forces the browser to redraw
+ const _ = window.getComputedStyle(style).opacity;
+ document.head.removeChild(style!);
+ }
+ };
+
+ useIsomorphicLayoutEffect(() => {
+ setMode(mode as T | BasicColorMode);
+ }, []);
+
+ return [
+ mode,
+ setMode
+ ];
+};