Agent integration guide
Import choices, integration patterns, migration checks and troubleshooting for coding agents.
This guide covers @wrksz/themes 2.x. Check your installed version and type declarations; online docs may include APIs from a newer release.
For a shorter reference, ask your agent to read node_modules/@wrksz/themes/AGENTS.md. Some agents skip instructions inside dependencies.
Plain Markdown · Documentation index · Full documentation
Set up themes with an AI agent
A ready prompt that points your agent at this guide. Paste it into Claude Code, Codex or any agent working in your repository.
Imports
Hooks or ClientThemeProvider: read/update state or create a nested scope
createThemes: export NextThemeProvider and matching hooks from one factory
Also render ThemeScript: apply the initial theme before hydration
Extended provider features
Extended provider features
ClientThemeProvider: set themeRoot in client code
The package root exports the client provider and factory without a bootstrap script. For Next.js, use /next in server layouts. That entry also exports server-only getTheme, so client modules must use /client or /next/create-themes.
Next.js layout and toggle
Keep the root layout on the server and leave existing auth or query providers in place.
import type { ReactNode } from "react";
import { ThemeProvider } from "@wrksz/themes/next";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class">{children}</ThemeProvider>
</body>
</html>
);
}"use client";
import { useTheme } from "@wrksz/themes/client";
export function ThemeToggle() {
const { resolvedTheme, forcedTheme, setTheme } = useTheme();
return (
<button
type="button"
disabled={!resolvedTheme || !!forcedTheme}
onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
>
Toggle theme
</button>
);
}The button renders the same label before hydration. For icons or text that depend on the theme, render a placeholder until useHydrated() returns true. suppressHydrationWarning on html covers only that element's attribute changes.
The default provider uses class, localStorage, key theme, light/dark themes and system detection. CSS must match the chosen attribute; the provider does not supply styles.
Typed configuration
"use client";
import { createThemes } from "@wrksz/themes/next/create-themes";
export const { NextThemeProvider, useTheme, useThemeValue } = createThemes({
themes: ["light", "dark", "high-contrast"] as const,
attribute: "class",
defaultTheme: "system",
});Import the provider and hooks from this same module. Each factory creates a separate context, so global hooks cannot read its provider. Call createThemes once at module scope.
Use the extended provider for systemThemeMap, themeRoot or enableSameDocumentSync; the factory does not accept these options. Custom system names such as paper/midnight need systemThemeMap on the extended provider, or enableSystem={false} with an explicit default.
Storage and server rendering
The Next.js provider applies the stored theme in the browser without calling cookies() on the server. Choose storage based on where you need the preference:
localStorageretains the preference and synchronizes browser tabs.sessionStorageretains it for the tab; the library does not subscribe to cross-tab updates.cookiemakes it readable by the server but does not synchronize tabs.hybridreads cookies first and mirrors writes to localStorage for cross-tab updates.noneavoids persistence, useful for independent widgets or forced themes.
getTheme() reads cookies, not localStorage, and cannot determine the browser's system preference. It can return "system", even with defaultTheme: "light" when the stored cookie contains "system". Do not copy an unresolved selection directly into html.className.
If server-rendered content needs the theme, read the cookie per request and pass the selection as initialTheme. With Cache Components, keep that read inside the app's request-time boundary. See getTheme and server themes.
In other SSR frameworks, render ThemeScript before theme-dependent content and match its themes, attribute, value, storage, storageKey and defaults to the client provider. See framework integration.
Antipatterns
Preserve attribute and value; next-themes defaults to data-theme, this package to class
@wrksz/themes/next into a client wrapperRender it in the server layout; keep unrelated client providers nested inside
Use one root provider; give independent scopes their own target and storage key
setTheme with an undeclared custom nameAdd the name to themes; invalid selections are ignored
resolvedTheme as if it always existsHandle undefined before hydration
Define onThemeChange and onStorageError in a client module; a typed Next factory can hold client callbacks
initialTheme as a controlled propUse setTheme for selection; initialization runs at mount
forcedTheme saves the user's preferenceIt overrides the active theme without writing storage; disable switching UI
useTheme().themes includes systemRender the system option separately when enableSystem is enabled
followSystem as ordinary system supportenableSystem offers a system option; followSystem ignores stored selection and follows later system changes
Changing initialTheme or storageKey does not reload stored state. To initialize again after switching accounts or workspaces, remount the provider.
Troubleshooting
useTheme must be used within its ThemeProviderCheck that the hook is below the matching provider and that the app uses one installed copy of the package
next/headers in a client moduleReplace the client module's /next import with /client or /next/create-themes
CSS selectors, attribute, mapped values, target existence and competing providers
The themes allowlist, forcedTheme, and custom system mappings
Initial HTML contains the bootstrap; CSP permits it; CSS selectors match; server and first client markup agree
initialTheme overrides storage; hybrid reads cookie before localStorage; inspect the actual keys
Storage mode, same origin and storage key; cookie/session/none do not use the cross-tab listener
Use the extended provider with enableSameDocumentSync; same key alone is insufficient
Browser restrictions or cookie options; observe onStorageError from client code
With CSP, pass the request's script nonce to authorize the bootstrap. It does not need unsafe-eval. Transition suppression also creates a style element; check style-src separately because a script nonce does not authorize styles.
Migration and verification
Follow the migration guide before removing next-themes.
Run the app's type check and production build. Test cold loads with empty and saved storage, then reload and use browser back/forward. Exercise the system option and any forced or scoped themes. For localStorage/hybrid, test a second tab; for extended same-document sync, test two providers.
Report the checks you ran in the consuming app, including any CSS, CSP or hydration issues that remain.