All files / hooks/use-preferred-color-scheme/src usePreferredColorScheme.ts

100% Statements 22/22
100% Branches 17/17
100% Functions 7/7
100% Lines 20/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88                                                      1x     15x                                                 12x   12x 8x 3x   5x     12x 8x 2x     6x 6x 9x     6x   6x 5x 5x     1x 1x     12x    
import { useEffect, useState } from "react";
 
/**
 * The user's preferred color scheme.
 */
export type ColorScheme = "light" | "dark";
 
/**
 * Options for the usePreferredColorScheme hook.
 */
export interface UsePreferredColorSchemeOptions {
  /**
   * Scheme returned on the server / when `matchMedia` is unavailable.
   * @default "light"
   */
  defaultScheme?: ColorScheme;
  /**
   * When `true` (default), the real `matchMedia` value is read synchronously on
   * the first client render. Set to `false` to render `defaultScheme` on the
   * first client render too and defer the real read to a post-commit effect —
   * this avoids a React hydration mismatch when the server rendered
   * `defaultScheme` but the user's system preference differs.
   * @default true
   */
  initializeWithValue?: boolean;
}
 
const DARK_QUERY = "(prefers-color-scheme: dark)";
 
function isSupported(): boolean {
  return (
    typeof window !== "undefined" && typeof window.matchMedia === "function"
  );
}
 
/**
 * Tracks the user's OS/browser color-scheme preference
 * (`prefers-color-scheme`), returning `"light"` or `"dark"` and updating live
 * when the system setting changes.
 *
 * This is the primitive that reflects the *system* preference; for a full
 * theme with manual override and persistence use `useDarkMode`.
 *
 * @param options - Configuration (`defaultScheme`, `initializeWithValue`)
 * @returns `"light"` or `"dark"`
 *
 * @example
 * ```tsx
 * const scheme = usePreferredColorScheme();
 * return <div className={scheme === "dark" ? "theme-dark" : "theme-light"} />;
 * ```
 */
export function usePreferredColorScheme(
  options: UsePreferredColorSchemeOptions = {}
): ColorScheme {
  const { defaultScheme = "light", initializeWithValue = true } = options;
 
  const [scheme, setScheme] = useState<ColorScheme>(() => {
    if (!initializeWithValue || !isSupported()) {
      return defaultScheme;
    }
    return window.matchMedia(DARK_QUERY).matches ? "dark" : "light";
  });
 
  useEffect(() => {
    if (!isSupported()) {
      return;
    }
 
    const mediaQueryList = window.matchMedia(DARK_QUERY);
    const handleChange = () => {
      setScheme(mediaQueryList.matches ? "dark" : "light");
    };
 
    handleChange();
 
    if (typeof mediaQueryList.addEventListener === "function") {
      mediaQueryList.addEventListener("change", handleChange);
      return () => mediaQueryList.removeEventListener("change", handleChange);
    }
 
    mediaQueryList.addListener(handleChange);
    return () => mediaQueryList.removeListener(handleChange);
  }, []);
 
  return scheme;
}