diff --git a/apps/www/content/docs/components/animated-theme-toggler.mdx b/apps/www/content/docs/components/animated-theme-toggler.mdx
index a3435e243..aa0f1b29e 100644
--- a/apps/www/content/docs/components/animated-theme-toggler.mdx
+++ b/apps/www/content/docs/components/animated-theme-toggler.mdx
@@ -107,14 +107,22 @@ The preview at the top of this page uses the default **circle** reveal. Below, e
+### Next.js (next-themes)
+
+Pass `theme` and `onThemeChange` to keep `useTheme()` subscribers in sync on toggle. When controlled, the component skips `localStorage` and lets the parent own persistence.
+
+
+
## Props
-| Prop | Type | Default | Description |
-| ------------ | ------------------- | ---------- | ------------------------------------------------------------------------ |
-| `className` | `string` | — | Additional classes for the button |
-| `duration` | `number` | `400` | Duration of the theme transition animation in milliseconds |
-| `variant` | `TransitionVariant` | `"circle"` | Shape used for the view-transition clip-path reveal |
-| `fromCenter` | `boolean` | `false` | If true, the clip expands from the viewport center instead of the button |
+| Prop | Type | Default | Description |
+| --------------- | ------------------------------------ | ---------- | ----------------------------------------------------------------------------------- |
+| `className` | `string` | — | Additional classes for the button |
+| `duration` | `number` | `400` | Duration of the theme transition animation in milliseconds |
+| `variant` | `TransitionVariant` | `"circle"` | Shape used for the view-transition clip-path reveal |
+| `fromCenter` | `boolean` | `false` | If true, the clip expands from the viewport center instead of the button |
+| `theme` | `"light" \| "dark"` | — | Controlled theme value. When set, the parent owns persistence (e.g. `next-themes`). |
+| `onThemeChange` | `(theme: "light" \| "dark") => void` | — | Called on toggle. Pair with `theme` for controlled usage. |
Export `TransitionVariant` from the same module when you need typed props or menus.
diff --git a/apps/www/public/llms-full.txt b/apps/www/public/llms-full.txt
index 6625eeef5..df1ee56f4 100644
--- a/apps/www/public/llms-full.txt
+++ b/apps/www/public/llms-full.txt
@@ -3091,6 +3091,13 @@ interface AnimatedThemeTogglerProps extends React.ComponentPropsWithoutRef<"butt
variant?: TransitionVariant
/** When true, the transition expands from the viewport center instead of the button center. */
fromCenter?: boolean
+ /**
+ * Controlled theme value. When provided, the parent owns persistence
+ * (e.g. `next-themes`) and this component will not write to localStorage.
+ */
+ theme?: "light" | "dark"
+ /** Called on toggle. Pair with `theme` for controlled usage. */
+ onThemeChange?: (theme: "light" | "dark") => void
}
function polygonCollapsed(cx: number, cy: number, vertexCount: number): string {
@@ -3202,15 +3209,21 @@ export const AnimatedThemeToggler = ({
duration = 400,
variant,
fromCenter = false,
+ theme,
+ onThemeChange,
...props
}: AnimatedThemeTogglerProps) => {
const shape = variant ?? "circle"
- const [isDark, setIsDark] = useState(false)
+ const isControlled = theme !== undefined
+ const [internalIsDark, setInternalIsDark] = useState(false)
+ const isDark = isControlled ? theme === "dark" : internalIsDark
const buttonRef = useRef(null)
useEffect(() => {
+ if (isControlled) return
+
const updateTheme = () => {
- setIsDark(document.documentElement.classList.contains("dark"))
+ setInternalIsDark(document.documentElement.classList.contains("dark"))
}
updateTheme()
@@ -3222,7 +3235,7 @@ export const AnimatedThemeToggler = ({
})
return () => observer.disconnect()
- }, [])
+ }, [isControlled])
const toggleTheme = useCallback(() => {
const button = buttonRef.current
@@ -3249,9 +3262,15 @@ export const AnimatedThemeToggler = ({
const applyTheme = () => {
const newTheme = !isDark
- setIsDark(newTheme)
+ // Always toggle the class synchronously so the View Transitions API
+ // snapshots the new theme inside the startViewTransition callback.
document.documentElement.classList.toggle("dark")
- localStorage.setItem("theme", newTheme ? "dark" : "light")
+ if (isControlled) {
+ onThemeChange?.(newTheme ? "dark" : "light")
+ } else {
+ setInternalIsDark(newTheme)
+ localStorage.setItem("theme", newTheme ? "dark" : "light")
+ }
}
if (typeof document.startViewTransition !== "function") {
@@ -3309,7 +3328,7 @@ export const AnimatedThemeToggler = ({
)
})
}
- }, [shape, fromCenter, duration, isDark])
+ }, [shape, fromCenter, duration, isDark, isControlled, onThemeChange])
return (