Migrating from next-themes
Step-by-step guide for migrating from next-themes to @wrksz/themes.
Last updated on
To migrate to @wrksz/themes 2.x, update the imports and check your CSS selectors and provider placement. The agent guide covers integration errors and testing.
Step 1: Install
These instructions cover 2.x. If you use 1.x, also follow Upgrading from 1.x.
pnpm add @wrksz/themesStep 2: Update the provider import
- import { ThemeProvider } from "next-themes";
+ import { ThemeProvider } from "@wrksz/themes/next";Preserve the existing themes, value, storageKey, defaultTheme and system settings. next-themes defaults to data-theme; @wrksz/themes defaults to class. If your CSS uses [data-theme="dark"], retain it explicitly:
<ThemeProvider attribute="data-theme">{children}</ThemeProvider>Keep attribute="class" if the app already uses it. Keeping storage="localStorage" and the same storageKey preserves saved preferences.
These defaults are documented in the next-themes API. Check the application's installed version and any local wrapper before migrating.
Upgrading from 1.x
Breaking · v2.0.0 These changes apply when upgrading from 1.x to 2.x. Skip this section if you already use 2.x.
What broke vs 1.2.0
| Change | 1.x | 2.0.0 |
|---|---|---|
Next ThemeProvider | async, called cookies() for storage="cookie"|"hybrid" and set initialTheme for you | Synchronous; does not read cookies on the server. |
| Server cookie read | Provider called cookies() for cookie/hybrid storage | Call getTheme explicitly when server content needs the selection; pass initialTheme to seed the provider |
| TypeScript peer | >=4.5.0 | >=5.9 (5.0–5.8 unsupported) |
forcedTheme | Could write the forced value into storage on init | Does not persist to storage |
initialTheme / storageKey | Prop changes could re-run mount init | Initializes at mount; later prop changes do not re-initialize |
Migration path
- For zero-flash without request-time APIs, keep
storage="cookie"or"hybrid". The pre-paint bootstrap still reads the cookie before hydration. - If server-rendered markup must know the theme, do this explicitly:
import { ThemeProvider, getTheme } from "@wrksz/themes/next";
export const instant = false;
export default async function RootLayout({ children }) {
const theme = await getTheme();
return (
<html suppressHydrationWarning>
<body>
<ThemeProvider storage="cookie" initialTheme={theme}>
{children}
</ThemeProvider>
</body>
</html>
);
}Step 3: Move the root provider to the server layout
@wrksz/themes/next is the server-layout entry and also exports the server-only getTheme helper. Move the root theme provider out of a client wrapper. Keep unrelated auth, query or UI providers in that wrapper and render it inside the new root theme provider.
Breaking · v2.0.0 In 1.x this entry was an async Server Component. From 2.0.0 it is synchronous, but it still must not be imported from a "use client" module because it re-exports getTheme.
- "use client";
- import { ThemeProvider } from "next-themes";
- export function Providers({ children }) {
- return <ThemeProvider>{children}</ThemeProvider>;
- }+ import { ThemeProvider } from "@wrksz/themes/next";
export default function RootLayout({ children }) {
return (
<html suppressHydrationWarning>
<body>
+ <ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}If you need a nested provider inside a Client Component, use ClientThemeProvider instead.
Step 4: Update client hook imports
- import { useTheme } from "next-themes";
+ import { useTheme } from "@wrksz/themes/client";API differences
Check your props against the provider reference:
themesis an allowlist: declare every custom name passed tosetThemeorforcedTheme.useTheme().themesreturns the configured list without automatically appending"system". Add a system menu option explicitly when system support is enabled.themeandresolvedThememay beundefinedbefore hydration. Preserve hydration guards for theme-dependent UI.- A nested provider creates its own state. Use distinct DOM targets and storage keys for independent sections.
- The root package entry is client-only. Use
/nextfor the Next.js root bootstrap and/clientfor client hooks.
onThemeChange
onThemeChange is an additional @wrksz/themes callback, not part of the documented next-themes provider API. If the old app used a wrapper with that name, inspect its behavior before replacing it.
Calling setTheme("system") reports the selected value "system". A later system-preference change while following the system reports the resolved value ("light" or "dark"). Handle both when persisting preferences.
Define callbacks in client code; do not pass an ordinary function from a Server Component through the Next.js provider. A client module using the Next.js typed factory can configure a callback while exporting a root NextThemeProvider.
disableTransitionOnChange
Accepts boolean | string. Passing a CSS transition string suppresses only those specific properties, keeping other transitions intact.
// next-themes
<ThemeProvider disableTransitionOnChange>
// @wrksz/themes - also accepts a CSS string
<ThemeProvider disableTransitionOnChange="background-color 0s, color 0s">New features
storage="hybrid"- cookie-first reads for SSR +localStoragemirror for cross-tab sync.storage="cookie"- persists the selection in a cookie; the bootstrap reads it before hydration.storage="sessionStorage"- persists theme only for the current tab.storage="none"- no persistence, useful for scoped or forced themes.themeColor- updates<meta name="theme-color">on theme change.initialTheme- initialize theme from a server-side source.followSystem- always follow system preference, ignoring stored value.getTheme()- read the current theme from a cookie outside React.createThemes(...)- creates a provider and hooks from one typed theme list. Use@wrksz/themes/next/create-themeswhen the root layout needsNextThemeProviderwithout re-listing the tuple.useThemeEffect(...)- side effects that run on theme changes after mount.
See Why not next-themes? for the full comparison.
Verify before removing next-themes
Search the application for remaining next-themes imports, including wrappers and tests. Remove the old dependency with the project's package manager only after replacing them.
Run the app's type check and production build. Test loading with empty and saved storage, then reload and use back/forward navigation. Check custom themes and forced routes if the app uses them. Confirm that CSS selectors match the DOM, hydration produces no warnings, and theme menus still offer the system option when enabled.