{"_id":"@brustack/next-theme-transitions","_rev":"5-fefe021bb5bf60eaa57ff8f2c138fd86","name":"@brustack/next-theme-transitions","dist-tags":{"latest":"1.1.3"},"versions":{"1.0.0":{"name":"@brustack/next-theme-transitions","version":"1.0.0","keywords":["nextjs","next","app-router","react-server-components","react","react-hook","hooks","theme","theme-toggle","theme-switcher","dark-mode","dark-theme","view-transitions","view-transitions-api","spread","fade","transition","animation"],"author":{"name":"Bruno Neckel"},"license":"MIT","_id":"@brustack/next-theme-transitions@1.0.0","maintainers":[{"name":"bncodes","email":"neckel.bw+bncodes@gmail.com"},{"name":"brunoneckel","email":"neckel.bw@gmail.com"}],"homepage":"https://github.com/brustack/theme-transitions#readme","bugs":{"url":"https://github.com/brustack/theme-transitions/issues"},"dist":{"shasum":"5d3242024dd9783660cc5be2d39a2de10a4f3608","tarball":"https://registry.npmjs.org/@brustack/next-theme-transitions/-/next-theme-transitions-1.0.0.tgz","fileCount":8,"integrity":"sha512-OLj2GB0ivhDFtH429LQ+OLKF26w9S6bt1t8PXShnHAj5v5V/0/m2XQFaXKLzPUfxcLzwnMepnjrCWudbiL8lkA==","signatures":[{"sig":"MEQCIA2PTfw7BDuc/NXGAIVDi1ZUsx2d5uPNubEYFWE0o8iYAiBb4JV5Ou39T/tHaNTcFtZ4QBhio2nwVeP0qem7vteKWA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":14198},"main":"./dist/index.mjs","type":"module","types":"./dist/index.d.mts","exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs"}},"gitHead":"e17d76877676a035f05be43df2ca61cbb68c9533","scripts":{"build":"unbuild && node scripts/verify-dist.mjs","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"brunoneckel","email":"neckel.bw@gmail.com"},"repository":{"url":"git+https://github.com/brustack/theme-transitions.git","type":"git","directory":"packages/next"},"_npmVersion":"11.16.0","description":"Next.js hook and anti-flash script component for animated dark/light theme transitions using the View Transitions API","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@brustack/theme-transitions-core":"^2.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","happy-dom":"^20.11.1","react-dom":"^19.2.8","@types/react":"^19.2.18","@types/react-dom":"^19.2.4","@testing-library/react":"^16.3.2","rollup-plugin-preserve-directives":"^0.4.0"},"peerDependencies":{"next":">=15.5.23","react":"^18.0.0 || ^19.0.0"},"_npmOperationalInternal":{"tmp":"tmp/next-theme-transitions_1.0.0_1786153936660_0.35362447872800873","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@brustack/next-theme-transitions","version":"1.1.0","keywords":["nextjs","next","app-router","react-server-components","react","react-hook","hooks","theme","theme-toggle","theme-switcher","dark-mode","dark-theme","view-transitions","view-transitions-api","spread","fade","transition","animation"],"author":{"name":"Bruno Neckel"},"license":"MIT","_id":"@brustack/next-theme-transitions@1.1.0","maintainers":[{"name":"bncodes","email":"neckel.bw+bncodes@gmail.com"},{"name":"brunoneckel","email":"neckel.bw@gmail.com"}],"homepage":"https://github.com/brustack/theme-transitions#readme","bugs":{"url":"https://github.com/brustack/theme-transitions/issues"},"dist":{"shasum":"d2790dffa4d0bfde81b8519e2631b0114a88b838","tarball":"https://registry.npmjs.org/@brustack/next-theme-transitions/-/next-theme-transitions-1.1.0.tgz","fileCount":8,"integrity":"sha512-qePLL+LZBxo3sg9xf8l/Oltn0Uw2pzhMEPl8AjRIRUPPuezugmjgWWc03wTN/voZ3xpOkGTP6d3KNtm+4G5jMw==","signatures":[{"sig":"MEUCIGCTHvcTg12NjH9VQpeMaoq0jTZj8lS9uGjn1wMEuNPRAiEA5xkYrxlo1XVx2nxk0LzWfUFbKa0fo4PaT1wWicwm3ag=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":14550},"main":"./dist/index.mjs","type":"module","types":"./dist/index.d.mts","exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs"}},"gitHead":"337469cc20d9eb7f8cd87f6e67eeb6037673146a","scripts":{"build":"unbuild && node scripts/verify-dist.mjs","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"brunoneckel","email":"neckel.bw@gmail.com"},"repository":{"url":"git+https://github.com/brustack/theme-transitions.git","type":"git","directory":"packages/next"},"_npmVersion":"11.16.0","description":"Next.js hook and anti-flash script component for animated dark/light theme transitions using the View Transitions API","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@brustack/theme-transitions-core":"^2.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","happy-dom":"^20.11.1","react-dom":"^19.2.8","@types/react":"^19.2.18","@types/react-dom":"^19.2.4","@testing-library/react":"^16.3.2","rollup-plugin-preserve-directives":"^0.4.0"},"peerDependencies":{"next":">=15.5.23","react":"^18.0.0 || ^19.0.0"},"_npmOperationalInternal":{"tmp":"tmp/next-theme-transitions_1.1.0_1786170490114_0.377377222344464","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@brustack/next-theme-transitions","version":"1.1.1","keywords":["nextjs","next","app-router","react-server-components","react","react-hook","hooks","theme","theme-toggle","theme-switcher","dark-mode","dark-theme","view-transitions","view-transitions-api","spread","fade","transition","animation","custom-themes"],"author":{"name":"Bruno Neckel"},"license":"MIT","_id":"@brustack/next-theme-transitions@1.1.1","maintainers":[{"name":"bncodes","email":"neckel.bw+bncodes@gmail.com"},{"name":"brunoneckel","email":"neckel.bw@gmail.com"}],"homepage":"https://github.com/brustack/theme-transitions#readme","bugs":{"url":"https://github.com/brustack/theme-transitions/issues"},"dist":{"shasum":"f9cd31d02b1cdb9500a766a32ca435bb81d71de0","tarball":"https://registry.npmjs.org/@brustack/next-theme-transitions/-/next-theme-transitions-1.1.1.tgz","fileCount":8,"integrity":"sha512-YrD9WK2bpc6zhYQI2qBuHq7SAMMGAb0sNdWX67r6QCgVKEoxBFSNwegQB/cHngbdQj56DSK+DL84vbn26p5mDQ==","signatures":[{"sig":"MEUCIQChN8SxpkelPugQFYtkKb255+fSo0O1qtYALCYWaX+fmAIgOEl1jHDZShfMfHVgdfpbBPUHmMvyNu3wzBXv/Zf+bJQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@brustack%2fnext-theme-transitions@1.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":14508},"main":"./dist/index.mjs","type":"module","types":"./dist/index.d.mts","exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs"}},"gitHead":"2bc2a22f2df2e0e2bfdc95948d058b063b65da06","scripts":{"build":"unbuild && node scripts/verify-dist.mjs","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3775aa5a-b227-4199-857b-a8477cb1ce22"}},"repository":{"url":"git+https://github.com/brustack/theme-transitions.git","type":"git","directory":"packages/next"},"_npmVersion":"11.16.0","description":"Next.js hook and anti-flash script component for animated theme transitions using the View Transitions API","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@brustack/theme-transitions-core":"^2.2.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","happy-dom":"^20.11.1","react-dom":"^19.2.8","@types/react":"^19.2.18","@types/react-dom":"^19.2.4","@testing-library/react":"^16.3.2","rollup-plugin-preserve-directives":"^0.4.0"},"peerDependencies":{"next":">=15.5.23","react":"^18.0.0 || ^19.0.0"},"_npmOperationalInternal":{"tmp":"tmp/next-theme-transitions_1.1.1_1786252833279_0.7736799809997053","host":"s3://npm-registry-packages-npm-production"}},"1.1.2":{"name":"@brustack/next-theme-transitions","version":"1.1.2","keywords":["nextjs","next","app-router","react-server-components","react","react-hook","hooks","theme","theme-toggle","theme-switcher","dark-mode","dark-theme","view-transitions","view-transitions-api","spread","fade","transition","animation","custom-themes"],"author":{"name":"Bruno Neckel"},"license":"MIT","_id":"@brustack/next-theme-transitions@1.1.2","maintainers":[{"name":"bncodes","email":"neckel.bw+bncodes@gmail.com"},{"name":"brunoneckel","email":"neckel.bw@gmail.com"}],"homepage":"https://github.com/brustack/theme-transitions#readme","bugs":{"url":"https://github.com/brustack/theme-transitions/issues"},"dist":{"shasum":"3c0bbac6e196b69b81cd11bc26e89bdcfaf423d6","tarball":"https://registry.npmjs.org/@brustack/next-theme-transitions/-/next-theme-transitions-1.1.2.tgz","fileCount":8,"integrity":"sha512-KsUYjtMwjJLx0YT17/1NvePzcYIIBTC8+mdX4kcSWlmnWp/CYz+YiOJokWKM6IQl27r2uI46LXBRLCkCED4RVg==","signatures":[{"sig":"MEUCIQD5DM2Gf/DpavYpLEkfhC0+BqCV7g5s5D0VKis45VYwCgIgKWDF511wCmhVsuG+YPxuFplTYpH3qXJGivSpKn63pHM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@brustack%2fnext-theme-transitions@1.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":14508},"main":"./dist/index.mjs","type":"module","types":"./dist/index.d.mts","exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs"}},"gitHead":"4e1a122fcbdd3e99860a2081ba60873fbd15d0d2","scripts":{"build":"unbuild && node scripts/verify-dist.mjs","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3775aa5a-b227-4199-857b-a8477cb1ce22"}},"repository":{"url":"git+https://github.com/brustack/theme-transitions.git","type":"git","directory":"packages/next"},"_npmVersion":"11.16.0","description":"Next.js hook and anti-flash script component for animated theme transitions using the View Transitions API","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@brustack/theme-transitions-core":"^3.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","happy-dom":"^20.11.1","react-dom":"^19.2.8","@types/react":"^19.2.18","@types/react-dom":"^19.2.4","@testing-library/react":"^16.3.2","rollup-plugin-preserve-directives":"^0.4.0"},"peerDependencies":{"next":">=15.5.23","react":"^18.0.0 || ^19.0.0"},"_npmOperationalInternal":{"tmp":"tmp/next-theme-transitions_1.1.2_1786264571032_0.19351910853448762","host":"s3://npm-registry-packages-npm-production"}},"1.1.3":{"name":"@brustack/next-theme-transitions","version":"1.1.3","description":"Next.js hook and anti-flash script component for animated theme transitions using the View Transitions API","author":{"name":"Bruno Neckel"},"license":"MIT","type":"module","sideEffects":false,"main":"./dist/index.mjs","types":"./dist/index.d.mts","exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs"}},"repository":{"type":"git","url":"git+https://github.com/brustack/theme-transitions.git","directory":"packages/next"},"publishConfig":{"access":"public"},"peerDependencies":{"next":">=15.5.23","react":"^18.0.0 || ^19.0.0"},"dependencies":{"@brustack/react-theme-transitions":"^2.2.0","@brustack/theme-transitions-core":"^3.1.0"},"devDependencies":{"@testing-library/react":"^16.3.2","@types/react":"^19.2.18","@types/react-dom":"^19.2.4","happy-dom":"^20.11.1","react":"^19.2.8","react-dom":"^19.2.8","rollup-plugin-preserve-directives":"^0.4.0"},"scripts":{"build":"unbuild && node scripts/verify-dist.mjs","prepublishOnly":"npm run build","typecheck":"tsc --noEmit"},"keywords":["nextjs","next","app-router","react-server-components","react","react-hook","hooks","theme","theme-toggle","theme-switcher","dark-mode","dark-theme","view-transitions","view-transitions-api","spread","fade","transition","animation","custom-themes"],"gitHead":"4845a776a8ab12fe65a58024ee8fbb6ba23d75fd","_id":"@brustack/next-theme-transitions@1.1.3","bugs":{"url":"https://github.com/brustack/theme-transitions/issues"},"homepage":"https://github.com/brustack/theme-transitions#readme","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-A0/4jqbFmsYHTasNAdJdF9SSfpqlIXqGpCboQ8R2pQwHTBdhzAmP5N1F4u45XXQKK/ZU0QFl7I0ClgNfBFtIjQ==","shasum":"14b0373579304c0f7e0e550ba2a524d95e5e1ff3","tarball":"https://registry.npmjs.org/@brustack/next-theme-transitions/-/next-theme-transitions-1.1.3.tgz","fileCount":8,"unpackedSize":13031,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@brustack%2fnext-theme-transitions@1.1.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDxcuRzS+wLONUHo7/PdchQvhqAK/dkUvw7DaNj412FRQIhALeZvU8fAj7viiLLEjDfOZ0+htW5HNv2EsioY3Dr0Ni8"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3775aa5a-b227-4199-857b-a8477cb1ce22"}},"directories":{},"maintainers":[{"name":"bncodes","email":"neckel.bw+bncodes@gmail.com"},{"name":"brunoneckel","email":"neckel.bw@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/next-theme-transitions_1.1.3_1786564424691_0.3937768378896631"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-08T01:52:16.495Z","modified":"2026-08-12T19:53:45.217Z","1.0.0":"2026-08-08T01:52:16.794Z","1.1.0":"2026-08-08T06:28:10.261Z","1.1.1":"2026-08-09T05:20:33.441Z","1.1.2":"2026-08-09T08:36:11.169Z","1.1.3":"2026-08-12T19:53:44.842Z"},"bugs":{"url":"https://github.com/brustack/theme-transitions/issues"},"author":{"name":"Bruno Neckel"},"license":"MIT","homepage":"https://github.com/brustack/theme-transitions#readme","keywords":["nextjs","next","app-router","react-server-components","react","react-hook","hooks","theme","theme-toggle","theme-switcher","dark-mode","dark-theme","view-transitions","view-transitions-api","spread","fade","transition","animation","custom-themes"],"repository":{"type":"git","url":"git+https://github.com/brustack/theme-transitions.git","directory":"packages/next"},"description":"Next.js hook and anti-flash script component for animated theme transitions using the View Transitions API","maintainers":[{"name":"bncodes","email":"neckel.bw+bncodes@gmail.com"},{"name":"brunoneckel","email":"neckel.bw@gmail.com"}],"readme":"# next-theme-transitions\n\n[![made by brustack](https://img.shields.io/badge/MADE%20BY%20brustack-000000.svg?style=for-the-badge&labelColor=000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMjYuNzcgMjI2Ljc3Ij48cG9seWdvbiBmaWxsPSIjRjRGMkVEIiBwb2ludHM9IjE1My43MyA4My4zOSAxNTMuNzMgMTUzLjczIDgzLjM5IDE1My43MyA4My4zOSAyMTMuNzMgMjEzLjczIDIxMy43MyAyMTMuNzMgODMuMzkgMTUzLjczIDgzLjM5Ii8%2BPHJlY3QgZmlsbD0iI0Y0RjJFRCIgeD0iODMuMzkiIHk9IjEzLjA0IiB3aWR0aD0iNjAiIGhlaWdodD0iNjAiLz48cmVjdCBmaWxsPSIjRjRGMkVEIiB4PSIxMy4wNCIgeT0iODMuMzkiIHdpZHRoPSI2MCIgaGVpZ2h0PSI2MCIvPjxyZWN0IGZpbGw9IiNGNEYyRUQiIHg9IjgzLjM5IiB5PSI4My4zOSIgd2lkdGg9IjYwIiBoZWlnaHQ9IjYwIi8%2BPC9zdmc%2BCg%3D%3D)](https://github.com/brustack)\n[![npm version](https://img.shields.io/npm/v/@brustack/next-theme-transitions.svg?style=for-the-badge&labelColor=000000)](https://www.npmjs.com/package/@brustack/next-theme-transitions)\n[![license](https://img.shields.io/npm/l/@brustack/next-theme-transitions.svg?style=for-the-badge&labelColor=000000)](https://github.com/brustack/theme-transitions/blob/main/packages/next/LICENSE)\n\nNext.js App Router hook and anti-flash script component for animated theme transitions using the View Transitions API.\n\n- ✅ Multiple effects to choose from\n- ✅ Zero flash of the wrong theme on load via a Server Component script\n- ✅ Syncs automatically with OS `prefers-color-scheme`\n- ✅ Custom themes beyond light/dark\n- ✅ Origin auto-derived from the click event, bind `toggleTheme` straight to `onClick`\n- ✅ App Router only, no Context, no Provider\n- ✅ No dependency on the React adapter\n\n<br>\n\n<p align=\"center\">\n  <img src=\"../../.github/assets/demo.gif\" alt=\"Demo: clicking anywhere on the page triggers an animated theme transition originating from the cursor\" />\n</p>\n\n<p align=\"center\">Check out the <a href=\"https://theme-transitions.brustack.dev\">Live Example</a> to try it for yourself.</p>\n\n## Install\n\n```sh\nnpm install @brustack/next-theme-transitions\n# or\npnpm add @brustack/next-theme-transitions\n# or\nyarn add @brustack/next-theme-transitions\n```\n\nApp Router only. Pages Router (`_app.tsx`/`_document.tsx`) isn't supported.\n\nPrefer to see it running first?\n\n```sh\ngit clone https://github.com/brustack/theme-transitions.git\ncd theme-transitions\nnpm run install:next-demo\nnpm run dev:next-demo\n```\n\n## Usage\n\nAdd `ThemeScript` to your root layout's `<head>`. This is what prevents a flash of the wrong theme on load; it renders a plain inline `<script>` on the server, no client JavaScript required for this part:\n\n```tsx\n// app/layout.tsx\nimport { ThemeScript } from '@brustack/next-theme-transitions';\nimport '@brustack/theme-transitions-core/style.css';\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\" suppressHydrationWarning>\n      <head>\n        <ThemeScript />\n      </head>\n      <body>{children}</body>\n    </html>\n  );\n}\n```\n\nThen use `useThemeTransition` from any Client Component:\n\n```tsx\n// app/components/theme-toggle.tsx\n'use client';\n\nimport { useThemeTransition } from '@brustack/next-theme-transitions';\n\nexport const ThemeToggle = () => {\n  const { theme, isAnimating, toggleTheme } = useThemeTransition();\n\n  return (\n    <button disabled={isAnimating} onClick={toggleTheme}>\n      {theme}\n    </button>\n  );\n};\n```\n\nBinding `toggleTheme` directly to `onClick` works because it accepts React's `MouseEvent` and derives the spread effect's origin from the click position automatically. The component calling the hook needs its own `'use client'` directive, same as any other interactive component in the App Router; `useThemeTransition` itself already has one, but that only makes the hook's *own* module client-safe, it doesn't make the component that calls it a Client Component.\n\n## Styling\n\n`ThemeScript`/`useThemeTransition` apply the current theme's name (`dark`, `light`, or a custom name, see Custom themes below) as a class on `<html>`. Style your palette off that class with any approach.\n\n### CSS variables\n\n```css\n:root {\n  --bg: #ffffff;\n  --text: #111111;\n}\n\nhtml.dark {\n  --bg: #0b0b10;\n  --text: #f4f2ed;\n}\n\nhtml.sepia {\n  --bg: #f4ecd8;\n  --text: #4b3621;\n}\n\nbody {\n  background: var(--bg);\n  color: var(--text);\n}\n```\n\n### Tailwind\n\nSet `darkMode: 'class'` in your Tailwind config (see Install above), then map your color tokens to the CSS variables above:\n\n```js\n// tailwind.config.js\nmodule.exports = {\n  darkMode: 'class',\n  theme: {\n    extend: {\n      colors: {\n        bg: 'var(--bg)',\n        text: 'var(--text)',\n      },\n    },\n  },\n};\n```\n\n### Styled-components (or any CSS-in-JS)\n\n```tsx\n// app/global-style.tsx\n'use client';\n\nimport { createGlobalStyle } from 'styled-components';\n\nexport const GlobalStyle = createGlobalStyle`\n  :root {\n    --bg: #ffffff;\n    --text: #111111;\n  }\n\n  html.dark {\n    --bg: #0b0b10;\n    --text: #f4f2ed;\n  }\n\n  html.sepia {\n    --bg: #f4ecd8;\n    --text: #4b3621;\n  }\n`;\n```\n\n```tsx\n// app/layout.tsx\nimport { GlobalStyle } from './global-style';\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\" suppressHydrationWarning>\n      <head>\n        <ThemeScript />\n      </head>\n      <body>\n        <GlobalStyle />\n        {children}\n      </body>\n    </html>\n  );\n}\n```\n\n`GlobalStyle` needs its own `'use client'` boundary, same as `ThemeToggle`. This mounts it, it doesn't set up styled-components' own SSR style injection for the App Router, see [styled-components' Next.js registry guide](https://styled-components.com/docs/advanced#nextjs) for that.\n\n## Configuration (optional)\n\n| Variant | `duration` | `easing` |\n|---|:---:|:---:|\n| `spread` | `'1s'` | ❌ |\n| `fade` (default) | `'400ms'` | `'ease'` |\n| `none` | ❌ | ❌ |\n\n`ThemeScript` and `useThemeTransition` accept the same shape. Pass matching options to both to set the app-wide default (the pre-hydration script and the interactive hook must agree):\n\n```tsx\n// app/layout.tsx\n<ThemeScript variant=\"spread\" duration=\"1s\" />\n```\n\n```tsx\n// app/components/theme-toggle.tsx\nuseThemeTransition({ variant: 'spread', duration: '1s' })\n```\n\nPass a `MouseEvent` (as shown in Usage) or an options object to `toggleTheme`/`setTheme` to override just that one call.\n\n### Custom themes\n\nRegister extra theme names beyond `light`/`dark`/`system` via `themes`, passed to both `ThemeScript` and `useThemeTransition`:\n\n```tsx\n<ThemeScript themes={['sepia', 'sunset']} />\n```\n\n```tsx\nuseThemeTransition({ themes: ['sepia', 'sunset'] })\n```\n\n`setTheme('sepia')` then applies a `sepia` class the same way `light`/`dark` do (see Styling above). The hook's `themes` array always includes `['light', 'dark', 'system', ...your custom names]` on the client; during SSR it only reports the 3 built-ins, since custom names aren't knowable at module-load time, the real list takes over once the client hydrates. `toggleTheme()` is unaffected, it always flips between `light` and `dark`.\n\n## API\n\n| | |\n|---|---|\n| `theme` | Current resolved theme: `'light'`, `'dark'`, or a custom theme name |\n| `mode` | Current preference: `'light'`, `'dark'`, `'system'`, or a custom theme name |\n| `isAnimating` | `true` while a transition is running |\n| `themes` | All registered theme names: `['light', 'dark', 'system', ...custom]` (built-ins only during SSR) |\n| `toggleTheme(eventOrOptions?)` | Switch between light and dark |\n| `setTheme(mode, eventOrOptions?)` | Set `light`, `dark`, `system`, or a custom theme name |\n\n## Notes\n\n- This package has no Context/Provider. `useThemeTransition` reads from `getController()`'s external singleton directly (via `useSyncExternalStore`), the same way every other adapter in this project does, so there's nothing to wrap your app in beyond `ThemeScript` in `<head>`.\n- App Router only. Next.js doesn't expose a way for a third-party package to inject into your root layout automatically the way a Vite plugin or a Nuxt module can, so `ThemeScript` has to be added explicitly, it can't be zero-config.\n- `suppressHydrationWarning` on `<html>` is required because `ThemeScript`'s injected script sets a class on `<html>` before React hydrates. That's what prevents the flash, but it also means React sees an attribute it didn't render, so `suppressHydrationWarning` avoids a harmless but noisy console warning about that one attribute. This is the same tradeoff every anti-flash-script-based theme library makes, e.g. `next-themes`.\n\n## Known issues\n\n- Chrome 150 has a regression where the `spread` effect's clip-path animation can render from the wrong position after the browser window moves between displays with different DPI/scaling. This is a Chrome bug, not something this package can work around. It's already fixed upstream and verified in Chrome Canary; the fix should reach the Stable channel in a future release. See [Chromium issue #535696703](https://issues.chromium.org/issues/535696703).","readmeFilename":"README.md"}