diff --git a/apps/v4/content/docs/dark-mode/index.mdx b/apps/v4/content/docs/dark-mode/index.mdx
index de7b6957f6b..72d54caf4eb 100644
--- a/apps/v4/content/docs/dark-mode/index.mdx
+++ b/apps/v4/content/docs/dark-mode/index.mdx
@@ -56,4 +56,20 @@ description: Adding dark mode to your site.
Remix
+
+
+ TanStack Start
+
diff --git a/apps/v4/content/docs/dark-mode/meta.json b/apps/v4/content/docs/dark-mode/meta.json
index 86a47e4c7f3..950e75665e5 100644
--- a/apps/v4/content/docs/dark-mode/meta.json
+++ b/apps/v4/content/docs/dark-mode/meta.json
@@ -1,4 +1,4 @@
{
"title": "Dark mode",
- "pages": ["index", "next", "vite", "astro", "remix"]
+ "pages": ["index", "next", "vite", "astro", "remix", "tanstack-start"]
}
diff --git a/apps/v4/content/docs/dark-mode/tanstack-start.mdx b/apps/v4/content/docs/dark-mode/tanstack-start.mdx
new file mode 100644
index 00000000000..36198b41ec2
--- /dev/null
+++ b/apps/v4/content/docs/dark-mode/tanstack-start.mdx
@@ -0,0 +1,191 @@
+---
+title: TanStack Start
+description: Adding dark mode to your TanStack Start app.
+---
+
+
+
+### 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).
+
+```tsx title="components/theme-provider.tsx" showLineNumbers
+import { createContext, useContext, useEffect, useState } from "react"
+import { ScriptOnce } from "@tanstack/react-router"
+
+type Theme = "dark" | "light" | "system"
+
+type ThemeProviderProps = {
+ children: React.ReactNode
+ defaultTheme?: Theme
+ storageKey?: string
+}
+
+type ThemeProviderState = {
+ theme: Theme
+ setTheme: (theme: Theme) => void
+}
+
+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)
+ setThemeState(
+ stored === "light" || stored === "dark" || stored === "system"
+ ? stored
+ : defaultTheme
+ )
+ setMounted(true)
+ }, [defaultTheme, storageKey])
+
+ useEffect(() => {
+ if (!mounted) return
+ applyTheme(theme)
+ }, [theme, mounted])
+
+ useEffect(() => {
+ if (!mounted || theme !== "system") return
+
+ const media = window.matchMedia("(prefers-color-scheme: dark)")
+ const onChange = () => applyTheme("system")
+ media.addEventListener("change", onChange)
+ return () => media.removeEventListener("change", onChange)
+ }, [theme, mounted])
+
+ const setTheme = (next: Theme) => {
+ localStorage.setItem(storageKey, next)
+ setThemeState(next)
+ }
+
+ return (
+
+ {getThemeScript(storageKey, defaultTheme)}
+ {children}
+
+ )
+}
+
+export function useTheme() {
+ const context = useContext(ThemeProviderContext)
+ if (context === undefined)
+ throw new Error("useTheme must be used within a ThemeProvider")
+ return context
+}
+```
+
+### Wrap your root layout
+
+Add the `ThemeProvider` to your root layout and add the `suppressHydrationWarning` prop to the `html` tag.
+
+```tsx {8,19,24-26} title="src/routes/__root.tsx" showLineNumbers
+import {
+ createRootRoute,
+ HeadContent,
+ Outlet,
+ Scripts,
+} from "@tanstack/react-router"
+
+import { ThemeProvider } from "@/components/theme-provider"
+
+export const Route = createRootRoute({
+ head: () => ({
+ // ...
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+```
+
+### Add a mode toggle
+
+Place a mode toggle on your site to toggle between light and dark mode.
+
+```tsx title="components/mode-toggle.tsx" showLineNumbers
+import { Moon, Sun } from "lucide-react"
+
+import { Button } from "@/components/ui/button"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { useTheme } from "@/components/theme-provider"
+
+export function ModeToggle() {
+ const { setTheme } = useTheme()
+
+ return (
+
+
+
+
+
+ setTheme("light")}>
+ Light
+
+ setTheme("dark")}>
+ Dark
+
+ setTheme("system")}>
+ System
+
+
+
+ )
+}
+```
+
+