diff --git a/src/app/layout.tsx b/src/app/layout.tsx index d5a47fa..55465ed 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -12,7 +12,14 @@ const THEME_INIT_SCRIPT = ` try { var v = localStorage.getItem("mercury.theme"); var mode = v === "light" ? "light-only" : v === "dark" ? "dark-only" : "system"; - document.documentElement.setAttribute("data-seed-color-mode", mode); + var el = document.documentElement; + el.setAttribute("data-seed-color-mode", mode); + // Seed's "system" mode resolves dark ONLY when data-seed-user-color-scheme + // reflects the OS preference — the CSS has no prefers-color-scheme media + // query of its own. Sync it from matchMedia so "system" actually follows + // the OS (light-only/dark-only ignore this attribute). + var dark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches; + el.setAttribute("data-seed-user-color-scheme", dark ? "dark" : "light"); } catch (e) {} })(); `; diff --git a/src/ds/ThemeToggle.tsx b/src/ds/ThemeToggle.tsx index 10999eb..bc1f0bc 100644 --- a/src/ds/ThemeToggle.tsx +++ b/src/ds/ThemeToggle.tsx @@ -39,9 +39,19 @@ function persistTheme(theme: MercuryTheme): void { } } +/** Seed's "system" color-mode resolves dark only via data-seed-user-color-scheme + * (there's no prefers-color-scheme media query in the tokens), so we sync it + * from matchMedia. light-only/dark-only ignore this attribute. */ +function syncUserScheme(): void { + if (typeof window === "undefined" || !window.matchMedia) return; + const dark = window.matchMedia("(prefers-color-scheme: dark)").matches; + document.documentElement.setAttribute("data-seed-user-color-scheme", dark ? "dark" : "light"); +} + function applyTheme(theme: MercuryTheme): void { if (typeof document === "undefined") return; document.documentElement.setAttribute("data-seed-color-mode", toColorMode(theme)); + syncUserScheme(); } export interface ThemeToggleProps { @@ -60,6 +70,17 @@ export function ThemeToggle({ labels = DEFAULT_LABELS }: ThemeToggleProps) { // DOM before this ever mounts, so both agree from the first paint. const [theme, setTheme] = React.useState(() => getStoredTheme()); + // Keep data-seed-user-color-scheme synced to the OS so "system" mode follows + // live OS light/dark changes without a reload. + React.useEffect(() => { + if (typeof window === "undefined" || !window.matchMedia) return; + const mq = window.matchMedia("(prefers-color-scheme: dark)"); + syncUserScheme(); + const onChange = () => syncUserScheme(); + mq.addEventListener?.("change", onChange); + return () => mq.removeEventListener?.("change", onChange); + }, []); + function handleChange(next: string) { const nextTheme = next as MercuryTheme; setTheme(nextTheme);