@wrksz/themesv0.7.3
API Reference

ThemeProvider

API reference for the ThemeProvider component.

Edit on GitHub

Last updated on

ThemeProvider wraps your app, injects an anti-flash script before hydration, and manages theme state via ClientThemeProvider.

Two variants are available depending on your framework:

Since v0.6.0
ImportMechanismUse when
@wrksz/themes/nextuseServerInsertedHTMLNext.js 16+ (no React 19 warning)
@wrksz/themesinline <script> in RSCOther frameworks
app/layout.tsx (Next.js)
import { ThemeProvider } from "@wrksz/themes/next";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}

ThemeProvider from @wrksz/themes/next is an async Server Component. It must be used directly in a server component (e.g. layout.tsx) - it cannot be wrapped in a "use client" component. If you are migrating from next-themes and have a wrapper like this, remove it and use ThemeProvider directly in your layout.

// ❌ will throw: async Client Component error
"use client";
import { ThemeProvider } from "@wrksz/themes/next";
export function Providers({ children }) {
  return <ThemeProvider>{children}</ThemeProvider>;
}
// ✅ use directly in layout.tsx
import { ThemeProvider } from "@wrksz/themes/next";
export default function RootLayout({ children }) {
  return (
    <html suppressHydrationWarning>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}

For nested providers inside Client Components, use ClientThemeProvider instead.

Props

PropTypeDefaultDescription
themesstring[]["light", "dark"]Available themes
defaultThemestring"system"Theme used when no preference is stored
forcedThemestring-Force a specific theme, ignoring user preference
initialThemestring-Server-provided theme that overrides storage on mount. User can still call setTheme to change it
enableSystembooleantrueDetect system preference via prefers-color-scheme
enableColorSchemebooleantrueSet native color-scheme CSS property
attribute"class" | "data-*" | ("class" | "data-*")[]"class"HTML attribute(s) to set on the target element
valueRecord<string, string>-Map theme names to attribute values
targetstring"html"Element to apply theme to ("html", "body", or a CSS selector)
storageKeystring"theme"Key used for storage
storage"localStorage" | "sessionStorage" | "cookie" | "none""localStorage"Where to persist the theme. "cookie" reads/writes document.cookie and with @wrksz/themes/next also reads server-side - zero-flash SSR without boilerplate. Since v0.7.0
disableTransitionOnChangeboolean | stringfalseTemporarily suppress CSS transitions when switching themes. true disables all transitions. Pass a CSS transition value (e.g. "background-color 0s, color 0s") to suppress only specific properties while keeping others (transforms, opacity, etc.) intact. Since v0.7.2
followSystembooleanfalseAlways follow system preference, ignores stored value on mount
themeColorstring | Record<string, string>-Update <meta name="theme-color"> on theme change
noncestring-CSP nonce for the inline script
onThemeChange(theme: string) => void-Called whenever the theme changes. Receives the selected value (may be "system"). When system preference changes while theme is "system", fires with the resolved value ("light" or "dark").

Examples

Custom themes

<ThemeProvider themes={["light", "dark", "high-contrast"]}>
  {children}
</ThemeProvider>

Data attribute instead of class

<ThemeProvider attribute="data-theme">
  {children}
</ThemeProvider>

Multiple classes per theme

Map a theme to multiple CSS classes using a space-separated value:

<ThemeProvider
  themes={["light", "dark", "dim"]}
  value={{ light: "light", dark: "dark high-contrast", dim: "dark dim" }}
>
  {children}
</ThemeProvider>

Force a theme per page

// app/dashboard/layout.tsx
<ThemeProvider forcedTheme="dark">
  {children}
</ThemeProvider>

Server-provided theme

Use initialTheme to initialize from a server-side source (database, session, cookie) on every mount:

// app/layout.tsx
export default async function RootLayout({ children }) {
  const userTheme = await getUserTheme();

  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider
          initialTheme={userTheme ?? undefined}
          onThemeChange={saveUserTheme}
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}
Since v0.7.0

When using @wrksz/themes/next, storage="cookie" automatically reads the theme cookie server-side and renders the correct class on <html> from the first byte - no flash for any user, no boilerplate:

app/layout.tsx
import { ThemeProvider } from "@wrksz/themes/next";

export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="dark"
          storage="cookie"
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

The provider reads document.cookie on the client and cookies() from next/headers on the server. Theme changes are written to the cookie automatically.

Cookie storage does not support cross-tab theme sync. If you need cross-tab sync, use localStorage with initialTheme.

Suppress transitions on theme change

Since v0.7.3

Disable all transitions briefly when switching themes to prevent visual jank:

<ThemeProvider disableTransitionOnChange>
  {children}
</ThemeProvider>

To keep some transitions intact (e.g. hover effects, animations) while only suppressing color-related ones:

<ThemeProvider disableTransitionOnChange="background-color 0s, color 0s, border-color 0s, fill 0s, stroke 0s">
  {children}
</ThemeProvider>

The string is injected as transition: <value> !important on all elements for two animation frames during the switch.

Always follow system preference

Use followSystem to ignore any stored value and always apply the system preference. Useful for apps where you want the theme to stay in sync with the OS without letting users override it:

<ThemeProvider followSystem>
  {children}
</ThemeProvider>

Unlike defaultTheme="system", which applies the system preference only on first visit and then stores the resolved value, followSystem re-reads prefers-color-scheme on every mount and ignores the stored value entirely.

Disable storage

<ThemeProvider storage="none" defaultTheme="dark">
  {children}
</ThemeProvider>

Meta theme-color

<ThemeProvider themeColor={{ light: "#ffffff", dark: "#0a0a0a" }}>
  {children}
</ThemeProvider>

Works with CSS variables too:

<ThemeProvider themeColor="var(--color-background)">
  {children}
</ThemeProvider>

On this page