Kanzo UI
Hooks

useKanzoTheme

Read and write the runtime theme preferences owned by KanzoThemeProvider.

Usage

import { useKanzoTheme } from "@kanzo-tech/ui";
const { radius, resolvedAppearance, set } = useKanzoTheme();

It throws outside a KanzoThemeProvider — there is no silent default, because a component reading a theme that nothing applies is a bug, not a fallback.

The provider writes the preferences to <html> as data-* attributes and toggles .dark. This hook is how you read that state and request a change; it never touches the DOM itself.

Preference and resolution

Three of the values come in pairs, because a preference can be the absence of one. appearance is "" while the user is following the OS; resolvedAppearance is always light or dark. Read the resolved value to draw something, and the preference only to say whether it was pinned.

themeByAppearance / resolvedTheme split the same way: an empty preference defers to whichever theme the tenant makes default. The preference is a map keyed by side, because a theme is a side — "which theme" without "on which side" does not name a choice.

Changing a preference

set takes a partial patch and merges it — pass one axis, not the whole object:

const { set } = useKanzoTheme();

set({ radius: "lg" });
set({ density: "compact", font: "geist" });

Appearance has its own setter, setAppearance, because a host theme manager (next-themes) may own that axis. Going through set({ appearance }) would bypass the host and leave the two disagreeing.

API Reference

useKanzoTheme

function useKanzoTheme(): ThemeContextValue;

Takes no arguments. Throws useKanzoTheme must be used within a <KanzoThemeProvider>. when no provider is above it.

Returns

FieldTypeWhat it is
appearanceAppearancePrefThe pinned side, or "" while the OS decides
resolvedAppearance"light" | "dark"The side actually applied — read this to draw
setAppearance(next: AppearancePref) => voidSets appearance, routing through a host theme manager if one is wired
radius"none" | "xs" | "sm" | "md" | "lg"Corner radius axis
density"default" | "compact" | "comfortable"Root font-size axis
fontstringSans typeface axis
monoFontstringMono typeface axis
themeByAppearancePartial<Record<Appearance, string>>Which theme this user wears on each side; {} defers to the tenant's default
set(patch: Partial<ThemePrefs>) => voidMerges a partial patch into the preferences and persists it
fontsFontOption[]The sans options the host wired, for a selector
monoFontsFontOption[]The mono options the host wired
themesThemeOption[]The themes the tenant published — [], never undefined. A flat list: a brand is a theme, so there is no second level
defaultThemestringThe name the applied side falls back to when the preference is empty
defaultThemeFor(side: Appearance) => stringThe same answer for either side — a tenant may publish { light, dark }, since a theme carries its own palette
resolvedThemestringThis side's preference, or defaultTheme
setTheme(theme: string, options?: { appearance?: Appearance }) => voidChoose a theme for one side — the applied one unless appearance names the other
retiredThemestring | nullA theme the tenant withdrew out from under this user, held for the session
corePrefsRecord<string, ResolvedPref & { decl }>Every core axis resolved through the tenant's policy — value, via, offered
sourcesPrefSourcesWhat this host published, in the shape a declaration names it by — themes
reset() => voidUnsets every axis, so the tenant's starting point applies again
sectionsRecord<string, Record<string, string>>What the user chose in the sections a host registered, keyed by namespace. Opaque: the core never parses it
sectionPrefsRecord<string, Record<string, ResolvedPref & { decl }>>Those same choices resolved — value, via, offered — each with its declaration beside it
setSectionPref(namespace, values: Record<string, string | undefined>) => voidWrite into one namespace. Every other namespace rides through untouched

The preference fields are exactly ThemePrefs, so set accepts any subset of them.

sections and sectionPrefs are not the same thing

sections is the stored map — what the user picked, and nothing else. sectionPrefs is what is applied, after a tenant's policy and the manifest's default have had their say, so the two disagree whenever a choice is pinned or withheld. Read sectionPrefs to draw anything; sections only to say what the user themselves chose. See Contributing a section.

KanzoThemeProviderProps is exported, so a wrapper can take the same props without restating them.

The rest of the theme surface, for a host that builds its own controls. KanzoTheme and its KanzoThemeProps set the axes on a subtree without a provider of their own; ThemeNotice and ThemeNoticeProps are the notice a reader gets when the theme they had chosen is gone, and ThemeRetiredCopy translates it — pass both title and the body composer or it ends up half translated. ThemeStorage is the two-method adapter the provider reads and writes through: get returns a sparse document, because a key is absent when nobody has chosen it, not when it is at its default. ThemeScriptOptions is what the blocking script takes, and its storageKey must match the provider's. AppearanceController is where a foreign appearance model enters — next-themes spells its third value "system" and this package spells it "", and that translation happens in exactly one place.

On this page