Providers & hooks
ThemeProvider, PresetProvider, useTheme, and usePreset.
ThemeProvider
Wraps your app with light/dark mode context via next-themes. Must be the outermost provider.
import { ThemeProvider } from "@codecanon/next-presets";
<ThemeProvider defaultTheme="system" storageKey="theme">
{children}
</ThemeProvider>Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Required. Your app content. |
defaultTheme | Theme | "system" | Initial theme when no persisted value exists. |
storageKey | string | "theme" | localStorage key used to persist the theme. |
attribute | string | string[] | "class" | HTML attribute(s) used to apply the theme. data-preset-theme is always added automatically. |
All other props are forwarded to next-themes ThemeProvider.
PresetProvider
Manages which color preset is active. Applies data-preset="<id>" to document.documentElement so the CSS variables take effect. Must be nested inside ThemeProvider.
import { ThemeProvider, PresetProvider } from "@codecanon/next-presets";
<ThemeProvider>
<PresetProvider storageKey="preset">
{children}
</PresetProvider>
</ThemeProvider>Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Required. Your app content. |
storageKey | string | "preset" | localStorage key used to persist the active preset. |
attributes | data-${string} | "data-preset" | HTML attribute set on <html> to activate a preset. |
preset | string | undefined | undefined | Controlled preset ID. Omit (or pass undefined) to use localStorage only — useful while the DB value is loading. |
onPresetChange | (preset: string | undefined) => void | undefined | Fires when the user selects a new preset. Use this to persist the value to your database. |
When no stored preset exists the attribute is not set — the base shadcn/ui styles apply.
useTheme()
Returns current theme state and setters. Must be called inside a <ThemeProvider>.
import { useTheme } from "@codecanon/next-presets";
const {
theme, // "light" | "dark" | "system"
setTheme, // (value: Theme) => void
resolvedTheme, // "light" | "dark" — the actual resolved value
systemTheme, // "light" | "dark" — OS preference
colorScheme, // "light" | "dark" — same as resolvedTheme
isDarkTheme, // boolean
themes, // ["light", "dark", "system"]
forcedTheme, // Theme | undefined
} = useTheme();Return values
| Property | Type | Description |
|---|---|---|
theme | Theme | undefined | Currently stored theme value. |
setTheme | (value: Theme) => void | Update the active theme. |
resolvedTheme | ColorScheme | undefined | Actual rendered mode — never "system". |
systemTheme | ColorScheme | undefined | OS-level color scheme preference. |
colorScheme | ColorScheme | Current color scheme ("light" or "dark"). |
isDarkTheme | boolean | true when the resolved theme is dark. |
themes | Theme[] | All available theme names. |
forcedTheme | Theme | undefined | Present when a route forces a specific theme. |
Example: toggle button
function DarkModeToggle() {
const { isDarkTheme, setTheme } = useTheme();
return (
<button onClick={() => setTheme(isDarkTheme ? "light" : "dark")}>
{isDarkTheme ? "☀️ Light" : "🌙 Dark"}
</button>
);
}usePreset()
Returns the active preset and controls. Must be called inside a <PresetProvider>.
import { usePreset } from "@codecanon/next-presets";
const {
preset, // string | undefined — active preset ID
setPreset, // (preset: string) => void
resetPreset, // () => void — clears the preset
} = usePreset();Return values
| Property | Type | Description |
|---|---|---|
preset | string | undefined | The currently active preset ID, or undefined if none is set. |
setPreset | (preset: string) => void | Switch to any preset by ID — built-in or custom. Persists to localStorage. |
resetPreset | () => void | Clears the active preset and removes the data-preset attribute. |
Example: preset switcher
import { usePreset, PRESETS } from "@codecanon/next-presets";
function PresetList() {
const { preset, setPreset } = usePreset();
return (
<ul>
{PRESETS.map(([key, label]) => (
<li key={key}>
<button
onClick={() => setPreset(key)}
style={{ fontWeight: preset === key ? "bold" : "normal" }}
>
{label}
</button>
</li>
))}
</ul>
);
}Controlled Preset (Database-backed)
PresetProvider supports a controlled mode for cases where the active preset is stored in a database. Pass preset and onPresetChange to take control of the value:
// app/providers.tsx
"use client"
import { useState, useEffect } from "react"
import { PresetProvider } from "@codecanon/next-presets"
export function Providers({ children }: { children: React.ReactNode }) {
const [dbPreset, setDbPreset] = useState<string | undefined>(undefined)
useEffect(() => {
fetchUserPreset().then(setDbPreset)
}, [])
async function handlePresetChange(preset: string | undefined) {
setDbPreset(preset) // optimistic update — no visual delay
await saveUserPreset(preset) // persist to DB in the background
}
return (
<PresetProvider preset={dbPreset} onPresetChange={handlePresetChange}>
{children}
</PresetProvider>
)
}How it works:
- Fast initial render — on first load,
presetisundefined(DB not yet fetched).PresetProviderfalls back to the localStorage value so the page renders with the correct preset immediately, with no flash. - DB value takes over — once
presetresolves to a string, it becomes the authoritative value and overrides localStorage. - User picks a preset —
onPresetChangefires immediately. Update your state optimistically (as shown above) so the UI switches with no delay while the DB write happens in the background. - localStorage stays in sync — every preset change (user-initiated or controlled) is written to localStorage, so the next page load is fast regardless of DB latency.
| Prop | Type | Description |
|---|---|---|
preset | string | undefined | Controlled preset ID. Omit (or pass undefined) to use localStorage only — useful while the DB value is loading. |
onPresetChange | (preset: string | undefined) => void | Fires when the user selects a new preset. Use this to persist the value to your database. |
Constants
import { PRESETS, PRESET_BY_ID } from "@codecanon/next-presets";
// Array of [id, label] tuples for all built-in presets
PRESETS; // [["nuteral", "Nuteral"], ["violet-bloom", "Violet Bloom"], ...]
PRESET_BY_ID; // { "nuteral": "Nuteral", "violet-bloom": "Violet Bloom", ... }