diff --git a/src/hooks/index.ts b/src/hooks/index.ts
index a98f8586..2b8c6c5e 100644
--- a/src/hooks/index.ts
+++ b/src/hooks/index.ts
@@ -6,6 +6,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..416ff9e4
--- /dev/null
+++ b/src/hooks/useColorMode/useColorMode.demo.tsx
@@ -0,0 +1,19 @@
+import { useColorMode } from './useColorMode';
+
+const Demo = () => {
+ const colorMode = useColorMode();
+
+ const toggleColorMode = () =>
+ colorMode.set(
+ colorMode.value === 'dark' ? 'light' : colorMode.value === 'light' ? 'auto' : 'dark'
+ );
+
+ return (
+ <>
+
+
Click to change the color mode
+ >
+ );
+};
+
+export default Demo;
diff --git a/src/hooks/useColorMode/useColorMode.ts b/src/hooks/useColorMode/useColorMode.ts
new file mode 100644
index 00000000..6710b2ce
--- /dev/null
+++ b/src/hooks/useColorMode/useColorMode.ts
@@ -0,0 +1,119 @@
+import { useEffect, useState } from 'react';
+
+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 BasicColorMode = 'auto' | 'dark' | 'light';
+
+/** The use color mode options */
+export interface UseColorModeOptions {
+ /** HTML attribute applying the target element */
+ attribute?: string;
+ /** Disable transition on switch */
+ disableTransition?: boolean;
+ /** The initial color mode */
+ initialValue?: BasicColorMode | MODE;
+ /** Prefix when adding value to the attribute */
+ modes?: Record;
+ /** CSS Selector for the target element applying to */
+ selector?: string;
+ /** Storage object, can be localStorage or sessionStorage */
+ storage?: 'localStorage' | 'sessionStorage';
+ /** Key to persist the data into localStorage/sessionStorage. Pass `null` to disable persistence */
+ storageKey?: string | null;
+ /**A custom handler for handle the updates. When specified, the default behavior will be overridden */
+ onChanged?: (
+ mode: BasicColorMode | MODE,
+ defaultHandler: (mode: BasicColorMode | MODE) => void
+ ) => void;
+}
+
+/** The use color mode return type */
+export interface UseColorModeReturn {
+ /** The value of the auto mode */
+ auto: BasicColorMode;
+ /** The current color mode value */
+ value: BasicColorMode | MODE;
+ /** Function to set the color mode */
+ set: (mode: BasicColorMode | MODE) => void;
+}
+
+/**
+ * @name useColorMode
+ * @description - Hook for get and set color mode (dark / light / customs) with auto data persistence.
+ * @category Browser
+ *
+ * @param {UseColorModeOptions} options The options for configuring color mode hook.
+ * @returns {UseColorModeReturn} The object containing the current color mode and a function to set the color mode.
+ */
+export const useColorMode = (
+ options?: UseColorModeOptions
+) => {
+ const {
+ selector = 'html',
+ attribute = 'class',
+ disableTransition = true,
+ initialValue = 'auto',
+ storageKey = 'reactuse-color-scheme',
+ modes = {},
+ storage: _storage = 'localStorage',
+ onChanged
+ } = options ?? {};
+
+ const storage = _storage === 'sessionStorage' ? sessionStorage : localStorage;
+
+ const [value, setValue] = useState(
+ storageKey
+ ? (storage.getItem(storageKey) as BasicColorMode | MODE | null) || initialValue
+ : initialValue
+ );
+
+ const updateHTMLAttrs = (mode: string) => {
+ const element = document.querySelector(selector);
+ if (!element) return;
+
+ const modeClasses = [...Object.values(modes), 'auto', 'dark', 'light'] as (
+ | BasicColorMode
+ | MODE
+ )[];
+
+ if (attribute === 'class') {
+ element.classList.remove(...modeClasses);
+ element.classList.add(mode);
+ } else {
+ element.setAttribute(attribute, mode);
+ }
+
+ if (disableTransition) {
+ const style = document.createElement('style');
+ style.textContent = CSS_DISABLE_TRANS;
+
+ document.head.appendChild(style);
+
+ (() => getComputedStyle(style).opacity)();
+
+ document.head.removeChild(style);
+ }
+ };
+
+ const auto = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
+
+ useEffect(() => {
+ const mode = value !== 'auto' ? value : auto;
+
+ if (storageKey) storage.setItem(storageKey, value);
+
+ const defaultOnChanged = (mode: BasicColorMode | MODE) => updateHTMLAttrs(mode);
+
+ onChanged ? onChanged(mode, defaultOnChanged) : defaultOnChanged(mode);
+ }, [value, storage, storageKey, onChanged]);
+
+ return { value, auto, set: setValue };
+};