From 73b71b4e59ce937092b5cc090dd69464c87e9ef8 Mon Sep 17 00:00:00 2001 From: all3f0r1 Date: Mon, 6 Apr 2026 08:54:37 +0200 Subject: [PATCH] fix(admin): apply data-mode attribute for system theme preference (#97) When theme is "system", ThemeProvider removed the data-mode attribute from , 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 --- .../admin/src/components/ThemeProvider.tsx | 28 +++++++++---------- packages/admin/src/styles.css | 9 ++---- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/packages/admin/src/components/ThemeProvider.tsx b/packages/admin/src/components/ThemeProvider.tsx index 22aeeb6..3e6a1a3 100644 --- a/packages/admin/src/components/ThemeProvider.tsx +++ b/packages/admin/src/components/ThemeProvider.tsx @@ -44,29 +44,16 @@ export function ThemeProvider({ children, defaultTheme = "system" }: ThemeProvid return theme; }); - // 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. - // 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"). + // Resolve the effective theme whenever the user preference changes 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") { - root.removeAttribute("data-mode"); setResolvedTheme(getSystemTheme()); } else { - root.setAttribute("data-mode", theme); setResolvedTheme(theme); } }, [theme]); - // Listen for system theme changes when in system mode + // Listen for OS preference changes when in system mode React.useEffect(() => { if (theme !== "system") return; @@ -79,6 +66,17 @@ export function ThemeProvider({ children, defaultTheme = "system" }: ThemeProvid return () => mediaQuery.removeEventListener("change", handler); }, [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) => { setThemeState(newTheme); localStorage.setItem(STORAGE_KEY, newTheme); diff --git a/packages/admin/src/styles.css b/packages/admin/src/styles.css index f365908..7dd5391 100644 --- a/packages/admin/src/styles.css +++ b/packages/admin/src/styles.css @@ -19,13 +19,10 @@ @custom-variant dark (&:where([data-mode="dark"], [data-mode="dark"] *)); /* - * When no data-mode is set (system preference mode), follow the OS preference. - * Kumo's base layer sets color-scheme: light on :root — override to light dark - * so the browser respects prefers-color-scheme until the user explicitly picks. + * ThemeProvider always sets data-mode to the resolved preference ("light" or "dark"), + * so Tailwind dark: utilities (mapped to [data-mode="dark"]) activate correctly + * for both explicit and system-preference themes. */ -:root:not([data-mode]) { - color-scheme: light dark; -} /** * Classic theme token overrides