From 01539fb4d735788d305f895899548ee77dfad60f Mon Sep 17 00:00:00 2001 From: shadcn Date: Tue, 21 Apr 2026 10:35:34 +0400 Subject: [PATCH] refactor: add getThemeScript --- .../content/docs/dark-mode/tanstack-start.mdx | 70 +++++++++++-------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/apps/v4/content/docs/dark-mode/tanstack-start.mdx b/apps/v4/content/docs/dark-mode/tanstack-start.mdx index 58d5043385..36198b41ec 100644 --- a/apps/v4/content/docs/dark-mode/tanstack-start.mdx +++ b/apps/v4/content/docs/dark-mode/tanstack-start.mdx @@ -5,7 +5,7 @@ description: Adding dark mode to your TanStack Start app. -## Create a theme provider +### Create a theme provider TanStack Start uses `ScriptOnce` from `@tanstack/react-router` to inject a script that runs before React hydrates, preventing flash of unstyled content (FOUC). @@ -26,56 +26,64 @@ type ThemeProviderState = { setTheme: (theme: Theme) => void } -const themeScript = `(function(){try{var t=localStorage.getItem('theme');if(t!=='light'&&t!=='dark'&&t!=='system'){t='system'}var d=matchMedia('(prefers-color-scheme: dark)').matches;var r=t==='system'?(d?'dark':'light'):t;var e=document.documentElement;e.classList.add(r);e.style.colorScheme=r}catch(e){}})();` +function getThemeScript(storageKey: string, defaultTheme: Theme) { + const key = JSON.stringify(storageKey) + const fallback = JSON.stringify(defaultTheme) + + return `(function(){try{var t=localStorage.getItem(${key});if(t!=='light'&&t!=='dark'&&t!=='system'){t=${fallback}}var d=matchMedia('(prefers-color-scheme: dark)').matches;var r=t==='system'?(d?'dark':'light'):t;var e=document.documentElement;e.classList.add(r);e.style.colorScheme=r}catch(e){}})();` +} const ThemeProviderContext = createContext({ theme: "system", setTheme: () => {}, }) +function applyTheme(theme: Theme) { + const root = document.documentElement + root.classList.remove("light", "dark") + + const resolved = + theme === "system" + ? window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light" + : theme + + root.classList.add(resolved) + root.style.colorScheme = resolved +} + export function ThemeProvider({ children, defaultTheme = "system", storageKey = "theme", }: ThemeProviderProps) { const [theme, setThemeState] = useState(defaultTheme) + const [mounted, setMounted] = useState(false) useEffect(() => { const stored = localStorage.getItem(storageKey) - if (stored === "light" || stored === "dark" || stored === "system") { - setThemeState(stored) - } - }, [storageKey]) + setThemeState( + stored === "light" || stored === "dark" || stored === "system" + ? stored + : defaultTheme + ) + setMounted(true) + }, [defaultTheme, storageKey]) useEffect(() => { - const root = document.documentElement - root.classList.remove("light", "dark") - - const resolved = - theme === "system" - ? window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light" - : theme - - root.classList.add(resolved) - root.style.colorScheme = resolved - }, [theme]) + if (!mounted) return + applyTheme(theme) + }, [theme, mounted]) useEffect(() => { - if (theme !== "system") return undefined + if (!mounted || theme !== "system") return const media = window.matchMedia("(prefers-color-scheme: dark)") - const onChange = () => { - const root = document.documentElement - root.classList.remove("light", "dark") - const resolved = media.matches ? "dark" : "light" - root.classList.add(resolved) - root.style.colorScheme = resolved - } + const onChange = () => applyTheme("system") media.addEventListener("change", onChange) return () => media.removeEventListener("change", onChange) - }, [theme]) + }, [theme, mounted]) const setTheme = (next: Theme) => { localStorage.setItem(storageKey, next) @@ -84,7 +92,7 @@ export function ThemeProvider({ return ( - {themeScript} + {getThemeScript(storageKey, defaultTheme)} {children} ) @@ -98,7 +106,7 @@ export function useTheme() { } ``` -## Wrap your root layout +### Wrap your root layout Add the `ThemeProvider` to your root layout and add the `suppressHydrationWarning` prop to the `html` tag. @@ -136,7 +144,7 @@ function RootComponent() { } ``` -## Add a mode toggle +### Add a mode toggle Place a mode toggle on your site to toggle between light and dark mode.