@wrksz/themesv2.0.2

Migrating from next-themes

Step-by-step guide for migrating from next-themes to @wrksz/themes.

Edit on GitHub

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/themes

Step 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

Change1.x2.0.0
Next ThemeProviderasync, called cookies() for storage="cookie"|"hybrid" and set initialTheme for youSynchronous; does not read cookies on the server.
Server cookie readProvider called cookies() for cookie/hybrid storageCall 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)
forcedThemeCould write the forced value into storage on initDoes not persist to storage
initialTheme / storageKeyProp changes could re-run mount initInitializes 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:
app/layout.tsx
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>;
- }
app/layout.tsx
+ 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:

  • themes is an allowlist: declare every custom name passed to setTheme or forcedTheme.
  • useTheme().themes returns the configured list without automatically appending "system". Add a system menu option explicitly when system support is enabled.
  • theme and resolvedTheme may be undefined before 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 /next for the Next.js root bootstrap and /client for 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 + localStorage mirror 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-themes when the root layout needs NextThemeProvider without 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.

On this page