fix(admin): apply data-mode attribute for system theme preference (#97)

When theme is "system", ThemeProvider removed the data-mode attribute
from <html>, expecting color-scheme: light dark to handle dark mode.
However, Tailwind dark: utilities are mapped to [data-mode="dark"] via
@custom-variant and do not respond to color-scheme, so dark mode never
activated for system preference users.

Decouple DOM synchronization from the theme preference by syncing
data-mode with resolvedTheme instead. This ensures data-mode is always
set to "light" or "dark" regardless of whether the user chose explicitly
or follows system preference.

Fixes #96

Co-authored-by: Matt Kane <mkane@cloudflare.com>
This commit is contained in:
all3f0r1
2026-04-06 08:54:37 +02:00
committed by GitHub
parent d2114523a5
commit 73b71b4e59
2 changed files with 16 additions and 21 deletions

View File

@@ -44,29 +44,16 @@ export function ThemeProvider({ children, defaultTheme = "system" }: ThemeProvid
return theme; return theme;
}); });
// Update DOM and resolved theme when theme changes. // Resolve the effective theme whenever the user preference changes
// Uses data-mode (not data-theme) for dark mode — kumo's convention.
// data-theme is reserved for visual identity overrides.
// Update DOM and resolved theme when theme changes.
// Uses data-mode (not data-theme) for dark mode — kumo's convention.
// data-theme is reserved for visual identity overrides (e.g. "classic").
React.useEffect(() => { React.useEffect(() => {
const root = document.documentElement;
// Apply classic visual identity at the root level
// so token overrides cascade to all kumo components including portals
root.setAttribute("data-theme", "classic");
if (theme === "system") { if (theme === "system") {
root.removeAttribute("data-mode");
setResolvedTheme(getSystemTheme()); setResolvedTheme(getSystemTheme());
} else { } else {
root.setAttribute("data-mode", theme);
setResolvedTheme(theme); setResolvedTheme(theme);
} }
}, [theme]); }, [theme]);
// Listen for system theme changes when in system mode // Listen for OS preference changes when in system mode
React.useEffect(() => { React.useEffect(() => {
if (theme !== "system") return; if (theme !== "system") return;
@@ -79,6 +66,17 @@ export function ThemeProvider({ children, defaultTheme = "system" }: ThemeProvid
return () => mediaQuery.removeEventListener("change", handler); return () => mediaQuery.removeEventListener("change", handler);
}, [theme]); }, [theme]);
// Sync DOM attributes with the resolved theme.
// data-mode drives Tailwind dark: utilities via @custom-variant.
// data-theme is reserved for visual identity overrides (e.g. "classic").
// Always set data-mode explicitly — relying on its absence + color-scheme
// does not activate Tailwind dark: utilities which require [data-mode="dark"].
React.useEffect(() => {
const root = document.documentElement;
root.setAttribute("data-theme", "classic");
root.setAttribute("data-mode", resolvedTheme);
}, [resolvedTheme]);
const setTheme = React.useCallback((newTheme: Theme) => { const setTheme = React.useCallback((newTheme: Theme) => {
setThemeState(newTheme); setThemeState(newTheme);
localStorage.setItem(STORAGE_KEY, newTheme); localStorage.setItem(STORAGE_KEY, newTheme);

View File

@@ -19,13 +19,10 @@
@custom-variant dark (&:where([data-mode="dark"], [data-mode="dark"] *)); @custom-variant dark (&:where([data-mode="dark"], [data-mode="dark"] *));
/* /*
* When no data-mode is set (system preference mode), follow the OS preference. * ThemeProvider always sets data-mode to the resolved preference ("light" or "dark"),
* Kumo's base layer sets color-scheme: light on :root — override to light dark * so Tailwind dark: utilities (mapped to [data-mode="dark"]) activate correctly
* so the browser respects prefers-color-scheme until the user explicitly picks. * for both explicit and system-preference themes.
*/ */
:root:not([data-mode]) {
color-scheme: light dark;
}
/** /**
* Classic theme token overrides * Classic theme token overrides