ClientThemeProvider
Theme provider for use inside Client Components.
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 script | Yes - prevents flash on load | No | No |
| Use in Server Components | Yes | No | No |
| Use in Client Components | No | Yes | Yes |
| Default props | Yes | Yes (nonce unused) | Yes, plus themeRoot, systemThemeMap, enableSameDocumentSync |
themeRoot | No | No | Yes. 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":
export default function LandingLayout({ children }) {
return (
<ThemeProvider forcedTheme="dark" target="#landing-root" storage="none">
<div id="landing-root">{children}</div>
</ThemeProvider>
);
}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.