Getting started

Wrap your app with providers and add a theme preset picker.

1. Import the stylesheets

Add both imports at your app root — the preset file must come first:

globals.css
@import "@codecanon/next-presets/default/nuteral.css";
@import "@codecanon/next-presets/styles.css";

The first import sets the default :root variables (the appearance before any preset is chosen). The second loads all preset overrides. Swap nuteral.css for any other preset ID to change the default.

2. Wrap your app with providers

Add ThemeProvider and PresetProvider at the root. PresetProvider must be nested inside ThemeProvider:

app/layout.tsx
import { ThemeProvider, PresetProvider } from "@codecanon/next-presets";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider>
          <PresetProvider>
            {children}
          </PresetProvider>
        </ThemeProvider>
      </body>
    </html>
  );
}

Both providers persist their state to localStorage and restore it on the next page load.

3. Add a preset picker

First install the picker component into your project:

pnpm shadcn@latest add https://registry.codecanon.dev/r/preset-picker

Then use PresetPicker, PresetPickerContent, PresetPickerSearch, and PresetPickerList together to render a slide-out theme selector:

import {
  PresetPicker,
  PresetPickerTrigger,
  PresetPickerContent,
  PresetPickerSearch,
  PresetPickerList,
  PresetPickerThemeToggleGroup,
} from "@/components/preset-picker";

export function ThemeSidebar() {
  return (
    <PresetPicker>
      <PresetPickerTrigger />
      <PresetPickerContent>
        <PresetPickerThemeToggleGroup />
        <PresetPickerSearch />
        <PresetPickerList />
      </PresetPickerContent>
    </PresetPicker>
  );
}

PresetPickerSearch handles text filtering and arrow-key / Enter navigation. PresetPickerList renders the filtered preset list with live mini-app previews.

4. Open the picker programmatically

Use usePresetPicker to toggle the sheet from any button or trigger inside a <PresetPicker>:

import {
  PresetPicker,
  PresetPickerContent,
  PresetPickerSearch,
  PresetPickerList,
  PresetPickerTrigger,
  PresetPickerThemeToggleGroup,
  usePresetPicker,
} from "@/components/preset-picker";

function CustomOpenButton() {
  const { toggleOpen } = usePresetPicker();
  return <button onClick={toggleOpen}>Change Preset</button>;
}

export function App() {
  return (
    <PresetPicker>
      <main className="my-app">
        {/* Option A — built-in trigger button */}
        <PresetPickerTrigger>Choose Preset</PresetPickerTrigger>
        {/* Option B — custom trigger (see above) */}
        <CustomOpenButton />
      </main>
      <PresetPickerContent>
        <PresetPickerThemeToggleGroup />
        <PresetPickerSearch />
        <PresetPickerList />
      </PresetPickerContent>
    </PresetPicker>
  );
}

5. Read and set the preset in code

import { usePreset } from "@codecanon/next-presets";

function PresetControls() {
  const { preset, setPreset, resetPreset } = usePreset();

  return (
    <div>
      <p>Active: {preset ?? "none"}</p>
      <button onClick={() => setPreset("violet-bloom")}>Violet Bloom</button>
      <button onClick={() => setPreset("catppuccin")}>Catppuccin</button>
      <button onClick={resetPreset}>Reset</button>
    </div>
  );
}

6. Read and set the theme mode

import { useTheme } from "@codecanon/next-presets";

function ThemeToggle() {
  const { isDarkTheme, setTheme } = useTheme();

  return (
    <button onClick={() => setTheme(isDarkTheme ? "light" : "dark")}>
      {isDarkTheme ? "Switch to light" : "Switch to dark"}
    </button>
  );
}

On this page