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

PropTypeDefaultDescription
childrenReactNodeRequired. Your app content.
defaultThemeTheme"system"Initial theme when no persisted value exists.
storageKeystring"theme"localStorage key used to persist the theme.
attributestring | 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

PropTypeDefaultDescription
childrenReactNodeRequired. Your app content.
storageKeystring"preset"localStorage key used to persist the active preset.
attributesdata-${string}"data-preset"HTML attribute set on <html> to activate a preset.
presetstring | undefinedundefinedControlled preset ID. Omit (or pass undefined) to use localStorage only — useful while the DB value is loading.
onPresetChange(preset: string | undefined) => voidundefinedFires 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

PropertyTypeDescription
themeTheme | undefinedCurrently stored theme value.
setTheme(value: Theme) => voidUpdate the active theme.
resolvedThemeColorScheme | undefinedActual rendered mode — never "system".
systemThemeColorScheme | undefinedOS-level color scheme preference.
colorSchemeColorSchemeCurrent color scheme ("light" or "dark").
isDarkThemebooleantrue when the resolved theme is dark.
themesTheme[]All available theme names.
forcedThemeTheme | undefinedPresent 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

PropertyTypeDescription
presetstring | undefinedThe currently active preset ID, or undefined if none is set.
setPreset(preset: string) => voidSwitch to any preset by ID — built-in or custom. Persists to localStorage.
resetPreset() => voidClears 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:

  1. Fast initial render — on first load, preset is undefined (DB not yet fetched). PresetProvider falls back to the localStorage value so the page renders with the correct preset immediately, with no flash.
  2. DB value takes over — once preset resolves to a string, it becomes the authoritative value and overrides localStorage.
  3. User picks a presetonPresetChange fires immediately. Update your state optimistically (as shown above) so the UI switches with no delay while the DB write happens in the background.
  4. 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.
PropTypeDescription
presetstring | undefinedControlled preset ID. Omit (or pass undefined) to use localStorage only — useful while the DB value is loading.
onPresetChange(preset: string | undefined) => voidFires 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", ... }

On this page