Migrating from next-themes
Step-by-step guide for migrating from next-themes to @wrksz/themes.
Last updated on
@wrksz/themes is a near drop-in replacement for next-themes. For most apps, migration is a single import change. This page covers that change and any behavioral differences to be aware of.
Step 1: Install
Current docs target 2.0.0-beta.1. Use @wrksz/themes@beta. For stable 1.x, install @wrksz/themes@1.2.0 and skip Upgrading from 1.x.
bun add @wrksz/themes@betanpm install @wrksz/themes@betapnpm add @wrksz/themes@betayarn add @wrksz/themes@betaStep 2: Update the provider import
- import { ThemeProvider } from "next-themes";
+ import { ThemeProvider } from "@wrksz/themes/next";That's it for most apps.
Upgrading from 1.x
Breaking · v2.0.0-beta.1 Breaking changes below ship in 2.0.0-beta.1 (npm install @wrksz/themes@beta). npm latest is still 1.2.0. Skip this section if you stay on 1.x.
What broke vs 1.2.0
| Change | 1.x | 2.0.0-beta.1 |
|---|---|---|
Next ThemeProvider | async, called cookies() for storage="cookie"|"hybrid" and set initialTheme for you | Sync passthrough. No request-time cookie read. |
SSR <html className={theme}> | Often came "for free" from the provider | You must call getTheme and pass initialTheme (and usually export const instant = false) |
| 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 | Mount init is sticky; 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 className={theme} suppressHydrationWarning>
<body>
<ThemeProvider storage="cookie" initialTheme={theme}>
{children}
</ThemeProvider>
</body>
</html>
);
}Step 3: Remove the "use client" wrapper
@wrksz/themes/next is the server-layout entry and also exports the server-only getTheme helper. If you have a client wrapper (common in Next.js next-themes setups), remove it.
Breaking · v2.0.0-beta.1 In 1.x this entry was an async Server Component. From 2.0.0-beta.1 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
All next-themes props are supported. The following props have changed behavior or are new:
onThemeChange
In next-themes, onThemeChange fires with the resolved theme (e.g. "dark") even when setTheme("system") is called.
In @wrksz/themes, onThemeChange fires with the selected value - which may be "system". System preference changes while the theme is "system" still fire with the resolved value.
// next-themes: setTheme("system") fires onThemeChange("dark")
// @wrksz/themes: setTheme("system") fires onThemeChange("system")If you are persisting the theme server-side via onThemeChange, handle "system" explicitly:
<ThemeProvider
onThemeChange={(theme) => {
if (theme !== "system") saveTheme(theme);
}}
>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
After migrating, these features are available without any additional configuration:
storage="hybrid"- cookie-first reads for SSR +localStoragemirror for cross-tab sync.storage="cookie"- zero-flash SSR without boilerplate. Passstorage="cookie"toThemeProviderfrom@wrksz/themes/next.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(...)- typed factory for provider + hooks from one canonical theme tuple.useThemeEffect(...)- side effects that run on theme changes after mount.
See Why not next-themes? for the full comparison.