@wrksz/themesv2.0.2
API Reference

ClientThemeProvider

Theme provider for use inside Client Components.

Edit on GitHub

Last updated on

ClientThemeProvider is the client-side counterpart of ThemeProvider. Use it when you need a nested provider inside a Client Component - ThemeProvider renders an inline <script> which is not allowed in Client Components.

Two client entries exist. The default stays small. Shadow DOM, custom system maps, and same-document sync live on the opt-in extended import.

"use client";

import { ClientThemeProvider } from "@wrksz/themes/client";

export function AdminShell({ children }: { children: React.ReactNode }) {
  return (
    <ClientThemeProvider forcedTheme="dark">
      {children}
    </ClientThemeProvider>
  );
}

ThemeProvider vs ClientThemeProvider

ThemeProvider (@wrksz/themes/next)ClientThemeProvider (@wrksz/themes/client)Extended (@wrksz/themes/client/extended-provider)
Renders inline scriptYes - prevents flash on loadNoNo
Use in Server ComponentsYesNoNo
Use in Client ComponentsNoYesYes
Default propsYesYes (nonce unused)Yes, plus themeRoot, systemThemeMap, enableSameDocumentSync
themeRootNoNoYes. Omitted from @wrksz/themes/next/extended

For your root layout, always use ThemeProvider. Use ClientThemeProvider only for nested providers deeper in the tree.

Props

The default @wrksz/themes/client entry accepts the same props as ThemeProvider, with one exception: the nonce prop is not used since ClientThemeProvider renders no inline script.

It does not accept themeRoot, systemThemeMap, or enableSameDocumentSync. Those belong on @wrksz/themes/client/extended-provider. See Shadow DOM for themeRoot.

Examples

Scoped theming

Apply an independent theme to a specific section of your app by combining target and storage="none":

app/landing/layout.tsx
export default function LandingLayout({ children }) {
  return (
    <ThemeProvider forcedTheme="dark" target="#landing-root" storage="none">
      <div id="landing-root">{children}</div>
    </ThemeProvider>
  );
}
app/dashboard/layout.tsx
export default function DashboardLayout({ children }) {
  return (
    <ThemeProvider forcedTheme="light" target="#dashboard-root" storage="none">
      <div id="dashboard-root">{children}</div>
    </ThemeProvider>
  );
}
#landing-root { --bg: #0a0a0a; --fg: #fafafa; }
#dashboard-root { --bg: #ffffff; --fg: #0a0a0a; }

For a client-owned Element or ShadowRoot (no selector, no bootstrap), use themeRoot on the extended client provider.

Nested provider in a Client Component

"use client";

import { ClientThemeProvider } from "@wrksz/themes/client";

export function Sidebar({ children }: { children: React.ReactNode }) {
  return (
    <ClientThemeProvider storageKey="sidebar-theme">
      {children}
    </ClientThemeProvider>
  );
}

Each ClientThemeProvider maintains its own independent theme state - nested providers do not affect each other.

Shadow DOM

Import the extended client provider and pass the ShadowRoot from attachShadow. There is no anti-flash script: the bootstrap cannot see a client-owned shadow tree.

import { ClientThemeProvider } from "@wrksz/themes/client/extended-provider";

<ClientThemeProvider themeRoot={shadowRoot} storage="none" defaultTheme="dark">
  {children}
</ClientThemeProvider>

Full walkthrough, :host(.dark) CSS, and why target is ignored when themeRoot is set: Shadow DOM.

On this page