API Reference
@themvora/next · Client
Next.js App Router integration: SSR-safe hydration, cookie persistence and zero-flash.
Functions
ClientThemeProvider<T extends ThemeDefinition<string>>(__namedParameters): Element
Returns Element
createNextThemePersistence(themes?, defaultTheme?, options): ThemeSelectionPersistenceAdapter | null
Client-side persistence for Next.js: mirrors the selection to
localStorage (cross-tab sync, offline) and to the theme cookies the server
reads on the next request.
| Parameter | Type | Description |
|---|---|---|
themes | readonly ThemeDefinition<string>[] (optional) | The registry used to compute the configuration fingerprint. |
defaultTheme | string (optional) | Fallback theme name used in the fingerprint. |
options | NextThemePersistenceOptions | Storage key and backing storage. |
Returns ThemeSelectionPersistenceAdapter | null — A persistence adapter, or null during SSR.
Blocked storage degrades, it does not throw. window.localStorage can
throw on access, and setItem throws when the quota is full. Both are
treated as "this browser cannot remember the selection": the adapter still
exists, so the cookie mirror keeps working and the server still resolves the
same theme, and nothing is thrown into a render. This matters in Next
specifically because ClientThemeProvider creates the adapter inside
useMemo during render — a throw there failed the whole client tree.
The null return is reserved for "not in a browser", which is the contract
every caller already handles.
ThemeBootstrap<T extends ThemeDefinition<string>>(__namedParameters): null
Returns null
ThemeInspector(__namedParameters?): Element
A floating developer tool that inspects the active theme.
Renders a fixed toggle button and an expandable panel showing the current theme name, family, mode, selection state, token values, and emitted CSS variables — useful while building themeable UI.
See also: useTheme
Returns Element
The panel opens above the toggle with a gap, stays dismissible via the
toggle, Escape, or clicking outside, and matches the provider's transition
duration for its own fade.
<ThemeInspector />ThemeModeButton(): Element
A minimal demo-style button that cycles the theme mode:
light → dark → system → light.
Renders a <button> whose label is the mode actually applied, with the
configured mode shown alongside it while that is "system". Cycling always
follows the configured mode, so a visitor who picked "system" stays on it.
Use it for demos and prototypes, or build your own control with useTheme.
See also: useTheme, useEffectiveThemeMode
Returns Element
The label reads useEffectiveThemeMode rather than useThemeMode
on purpose. With a sunrise/sunset schedule running, the configured mode is the
visitor's standing request — "system", or whichever mode they last picked —
and it does not move at sunset. A label bound to it therefore kept announcing
the old mode while the page changed colour. The effective mode moves with the
page.
<ThemeModeButton />ThemeScope(__namedParameters): Element
Applies a theme to a subtree without replacing the global runtime.
The scope resolves local themes (themes prop) before falling back to the
parent runtime's theme registry. Nested scopes override their parent within
their own boundary. Theme changes are reactive: theme/family/mode
props and global mode changes re-resolve and animate in place without
remounting.
See also: useScopedTheme
Returns Element
Scope-local transitions inherit the parent transition configuration unless
explicitly overridden or disabled. First paint is server-safe: explicit
selections ship resolved variables inline, while OS-dependent selections
(system mode / family-following scopes) ship no inline variables plus a
@media (prefers-color-scheme: dark) override, so no flash occurs.
<ThemeScope family="plum" mode="dark">
<Editor />
</ThemeScope>useGeneratedTheme<T extends ThemeDefinition<string>>(): T
The theme produced by generateTheme() and registered with the provider.
Returns the registered theme that carries the generator's origin marker
(meta.generation.kind === "generated") — never a shipped preset, and never
inferred from a theme's id, family or colors. Generate one and pass the result
to ThemeProvider:
const generated = generateTheme({ seed: "#635BFF", family: "brand" });
<ThemeProvider themes={[generated.light, generated.dark]}>{children}</ThemeProvider>When both halves of one generateTheme() call are registered, the theme the
active selection resolves to is returned. If several generated themes are
registered and none is active, the call throws rather than guessing.
Throws ThemeError with code TV_GENERATION_THEME_NOT_FOUND when the provider
holds no generated theme, and TV_GENERATION_THEME_AMBIGUOUS when several are
registered and the active selection does not identify one.
Re-resolves on every settled change to the registry, store, or selection; if the generated theme is later removed, the same error is raised at the next render rather than a stale theme being returned.
See also: useThemeValue
Returns T — The registered generated theme.
useProvidedThemes<T extends ThemeDefinition<string>>(): readonly ProvidedThemeFamily<T>[]
The themes this provider was given, grouped by family.
Returns exactly the collection supplied to the nearest ThemeProvider — not
the built-in catalog, and not every theme that exists in the project. Build a
theme picker from it instead of enumerating a separate preset source:
const themes = useProvidedThemes();
return themes.map((family) => (
<section key={family.id}>
<h3>{family.name}</h3>
{family.variants.map((variant) => (
<button key={variant.name} onClick={() => setFamily(family.family)}>
{variant.mode}
</button>
))}
</section>
));The collection is reactive: a themes prop change, a runtime.use(pack)
install or a registry mutation re-renders the consumer. Entries keep their
identity while their content is unchanged, so React.memo and dependency
arrays behave.
See also: useTheme, normalizeProvidedThemes
Returns readonly ProvidedThemeFamily<T>[] — The provider's theme families, in supplied order.
useScopedTheme(ref, selection, transition?): RefObject<{ destroy: void; getTheme: void; refresh: void; setLocalThemes: void; setTransition: void; update: void } | null>
Headless scoped-theming hook: applies a scoped theme selection to an arbitrary element (by ref) without rendering a wrapper component.
Useful when the scoped element is not a plain div — e.g. a custom
component that forwards a ref — or when ThemeScope's wrapper would break
layout.
Must be called inside ThemeProvider.
| Parameter | Type | Description |
|---|---|---|
ref | RefObject<HTMLElement | null> | Ref to the element that receives the scoped theme variables. |
selection | ScopedThemeSelection | null | Scoped selection to apply. Pass null to remove the scope (destroys any active binding). |
transition | boolean | ThemeTransitionOptions (optional) | Transition override for this scope's changes. Inherits the provider transition when omitted. |
Returns RefObject<{ destroy: void; getTheme: void; refresh: void; setLocalThemes: void; setTransition: void; update: void } | null> — A ref holding the active scoped binding (for advanced access).
const elRef = useRef<HTMLDivElement>(null);
useScopedTheme(elRef, { family: "plum", mode: "dark" });
return <div ref={elRef} />;useSetThemeFamily<T extends ThemeDefinition<string>>(): __type(family: ThemeFamilies<readonly T[]>): void
Get a stable setFamily function (does not re-render on change).
See also: useThemeFamily
Returns __type(family: ThemeFamilies<readonly T[]>): void
useSetThemeMode<T extends ThemeDefinition<string>>(): __type(mode: ModesOf<T>): void
Get a stable setMode function (does not re-render on change).
See also: useThemeMode
Returns __type(mode: ModesOf<T>): void
useTheme<T extends ThemeDefinition<string>>(): { effectiveMode: "light" | "dark"; family: string; mode: ThemeMode; setFamily: __type(family: ThemeFamilies<readonly T[]>): void; setMode: __type(mode: ModesOf<T>): void; theme: T; toggleTheme: __type(): void }
The primary Themvora hook. Returns the current theme, mode, family and the selection controls.
When you pass the theme tuple element type, setFamily and setMode
are constrained to the families/modes defined in your registry (the
built-in set is used below so the example stands alone):
import { getBuiltInThemes } from "@themvora/core";
const themes = getBuiltInThemes();
const { theme, mode, family, setMode, setFamily, toggleTheme } = useTheme<typeof themes[number]>();
setFamily("mint"); // autocomplete suggests your families
setMode("dark");See also: useThemeValue, useThemeMode, useEffectiveThemeMode, useThemeFamily
Returns { effectiveMode: "light" | "dark"; family: string; mode: ThemeMode; setFamily: __type(family: ThemeFamilies<readonly T[]>): void; setMode: __type(mode: ModesOf<T>): void; theme: T; toggleTheme: __type(): void }
useThemeBatch(): __type(callback: __type(): void): void
Get a batch function that defers all selection changes and DOM writes to a single flush.
See also: useTheme
Returns __type(callback: __type(): void): void
useThemeFamily(): string
Subscribe to the current selection family.
See also: useSetThemeFamily
Returns string
useThemeHistory(): { canRedo: boolean; canUndo: boolean; clear: __type(): void; redo: __type(): void; undo: __type(): void }
Subscribe to the runtime history (undo/redo/canUndo/canRedo/clear).
See also: useThemeTimeTravel
Returns { canRedo: boolean; canUndo: boolean; clear: __type(): void; redo: __type(): void; undo: __type(): void }
useThemeLifecycle<T extends ThemeDefinition<string>>(): { on: __type<K extends keyof ThemeLifecycleEventMap<ThemeDefinition<string>>>(event: K, listener: __type(data: ThemeLifecycleEventMap<T>[K]): void): __type(): void }
Subscribe to runtime lifecycle events (theme changed, mode changed, …).
The handler is typed per event name, so on("beforeThemeChange", ({ next }) => …)
gives you the theme definitions rather than unknown — the same precision
runtime.lifecycle.on has in @themvora/core.
See also: useTheme
Returns { on: __type<K extends keyof ThemeLifecycleEventMap<ThemeDefinition<string>>>(event: K, listener: __type(data: ThemeLifecycleEventMap<T>[K]): void): __type(): void }
useThemeMode(): ThemeMode
Subscribe to the current selection mode ("light" | "dark" | "system").
This is the mode the visitor asked for. While a sunrise/sunset schedule
is running, or while the selection is "system", the appearance on screen can
differ from it — use useEffectiveThemeMode to display what is actually
applied.
See also: useEffectiveThemeMode, useSetThemeMode
Returns ThemeMode
useThemePacks(): __type(pack: ThemePack<any>): void
Get a function that applies a theme pack to the runtime.
See also: useTheme
Returns __type(pack: ThemePack<any>): void
useThemeRestore(): __type(snapshot: ThemeRuntimeSnapshot): void
Get a restore function that re-applies a previously captured snapshot.
See also: useThemeSnapshot
Returns __type(snapshot: ThemeRuntimeSnapshot): void
useThemeRuntime<T extends ThemeDefinition<string>>(): ThemeRuntime<T>
Get the active Themvora runtime from context. Throws when used outside a
ThemeProvider. Pass the theme tuple element type to type the runtime's
store/selection against your registry — any registry works, so the built-in
set is used here to keep the example self-contained:
import { getBuiltInThemes } from "@themvora/core";
const themes = getBuiltInThemes();
const runtime = useThemeRuntime<typeof themes[number]>();See also: ThemeProvider, useTheme
Returns ThemeRuntime<T>
useThemeSchedule(): ThemeSchedule | null
Reactive access to the runtime's sunrise/sunset scheduling controller.
Requires the runtime to be created with the scheduled option (see
ThemeProvider / createThemeRuntime). Returns null when the provider has
no schedule configured.
const schedule = useThemeSchedule();
schedule?.enable();
schedule?.disable();
// schedule.enabled, schedule.active, schedule.sunrise, schedule.sunset,
// schedule.nextTransition ... re-render reactively.See also: ThemeProvider, useTheme
Returns ThemeSchedule | null
The returned controller is a reactive view: its state reads come from the subscription below, not from the live controller. That is what makes it safe to render during SSR — the subscription hydrates from EMPTY_THEME_SCHEDULE_STATE, the same snapshot the server rendered, and only adopts the live state after mount. Returning the raw controller instead (which is what this hook used to do, discarding the subscription it had just created) handed consumers the live value during the hydration render, so React saw a mismatch against the server HTML and re-rendered the tree.
useThemeSnapshot(): __type(): ThemeRuntimeSnapshot
Get a snapshot function that captures the full runtime state.
See also: useThemeRestore
Returns __type(): ThemeRuntimeSnapshot
useThemeTimeTravel(): { history: HistoryEntry<ThemeDefinition<string>>[]; jump: __type(index: number): void }
Subscribe to the history timeline and get a jump(index) function.
See also: useThemeHistory
Returns { history: HistoryEntry<ThemeDefinition<string>>[]; jump: __type(index: number): void }
useThemeTokens<T extends ThemeDefinition<string>>(): ThemeTokens | undefined
Subscribe to the current theme's token groups.
See also: useThemeValue
Returns ThemeTokens | undefined
useThemeValue<T extends ThemeDefinition<string>>(): T
Subscribe to the current theme definition (re-renders on change).
See also: useTheme
Returns T
useToggleTheme(): __type(): void
Get a stable toggleTheme function (flips light ⇄ dark).
See also: useThemeMode
Returns __type(): void
Interfaces
ClientThemeProviderProps<T extends ThemeDefinition>
| Member | Type | Description |
|---|---|---|
adapters (optional) | ThemeAdapter<T>[] | Library adapters installed onto the created runtime. Each adapter keeps its own injected <style> in sync as the theme changes. |
children | ReactNode | — |
defaultTheme (optional) | ThemeIdentifier<T> | Fallback theme when no selection is persisted. Accepts a theme name ("cocoa-light") or a family ("cocoa"). |
initial | InitialThemeResolution<T> | — |
scheduled (optional) | false | ScheduledThemeOptions<T> | Sunrise/sunset scheduling. Created client-side only — the server resolves the initial theme (zero-flash) and the client schedule takes over activation. Configure via useThemeSchedule(). |
themeOverride (optional) | ThemeOverride<ThemeIdentifier<T>> | An explicit theme override applied over the cookie-backed selection. Applied once per value, never written to the theme cookies, and it does not fight a later manual selection. |
themes (optional) | readonly T[] | — |
transition (optional) | boolean | ThemeTransitionOptions | — |
NextThemePersistenceOptions
| Member | Type | Description |
|---|---|---|
key (optional) | string | — |
storage (optional) | Storage | — |
ThemeBootstrapProps<T extends ThemeDefinition>
| Member | Type | Description |
|---|---|---|
initial | InitialThemeResolution<T> | — |
themes | readonly T[] | — |
Related docs
- Families & Modes — A selection is a family (which palette) plus a mode (light/dark/system); every framework exposes the same read/write surface for it.
- Theme Discovery & Overrides — A provider exposes exactly the themes it was given — grouped by family, with variants and resolved tokens — and accepts an explicit one-shot themeOverride that outranks a saved selection.
- Runtime — One runtime per app owns the store, selection, registry, history, lifecycle and adapters; every framework binding is a thin view over it.
- ThemeScope — Apply a different family/mode (or a local theme definition) to a subtree, with scoped CSS variables and pre-paint support.
- Theme generation — Generate a complete, accessible light/dark theme pair from a single seed color, ready to drop into the runtime.
- Zero-flash bootstrap — Paint the correct theme on the first frame: a blocking script plus server-resolved initial state, with a Vite plugin for SPA builds.
- DevTools — Inspect and drive the live runtime from a panel: adapters, history, snapshots and performance entries.
- Next.js — the framework integration