Overview
Kuzenbo’s theming system provides seamless dark/light mode support with server-side rendering, cookie persistence, and system preference detection.
Installation
npm install @kuzenbo/theme next-themes
Quick Start
import { ThemeProvider } from "@kuzenbo/theme";
export default function App({ children }) {
return (
<ThemeProvider>
{children}
</ThemeProvider>
);
}
ThemeProvider
Wrapper around next-themes with Kuzenbo-specific defaults.
Props
The application tree to wrap with theme context.
Initial theme when no preference is stored. Valid values: "light", "dark", or "system".
HTML attribute to use for theme switching. Kuzenbo uses class and applies a .dark class.
Enable system preference detection via prefers-color-scheme media query.
storageKey
string
default:"kuzenbo-theme"
LocalStorage key for persisting theme preference.
disableTransitionOnChange
Prevents CSS transitions during theme changes to avoid flash.
Usage with next-themes
import { ThemeProvider } from "@kuzenbo/theme";
import { useTheme } from "next-themes";
function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
Toggle theme
</button>
);
}
export default function App({ children }) {
return (
<ThemeProvider>
<ThemeToggle />
{children}
</ThemeProvider>
);
}
Theme Bootstrap
Server-side theme initialization utilities for SSR frameworks.
ThemeBootstrapScript
Inline script component that applies theme before hydration to prevent flash.
import { ThemeBootstrapScript } from "@kuzenbo/theme";
export default function RootLayout({ children }) {
return (
<html suppressHydrationWarning>
<head>
<ThemeBootstrapScript />
</head>
<body>{children}</body>
</html>
);
}
Props:
defaultThemeSetting
ThemeSetting
default:"system"
Fallback theme when no preference is stored.
getThemeBootstrapScript
Generate the bootstrap script string for manual injection.
import { getThemeBootstrapScript } from "@kuzenbo/theme";
const scriptContent = getThemeBootstrapScript({
defaultThemeSetting: "system"
});
Parameters:
options: ThemeBootstrapScriptOptions
defaultThemeSetting: ThemeSetting (optional)
Returns: string - Script content
Theme Utilities
applyThemeToRootElement
Programmatically apply theme to document root.
import { applyThemeToRootElement } from "@kuzenbo/theme";
applyThemeToRootElement("dark");
// Adds .dark class and sets color-scheme: dark
Parameters:
theme: ThemePreference ("light" or "dark")
persistThemeCookie
Save theme preference to cookie.
import { persistThemeCookie } from "@kuzenbo/theme";
persistThemeCookie("dark");
// Sets cookie: kuzenbo-theme=dark; Path=/; Max-Age=31536000; SameSite=Lax
Parameters:
readThemeFromCookieString
Extract theme preference from cookie string (server-side).
import { readThemeFromCookieString } from "@kuzenbo/theme";
import { cookies } from "next/headers";
const cookieStore = await cookies();
const cookieString = cookieStore.toString();
const theme = readThemeFromCookieString(cookieString);
// Returns "light" | "dark" | null
Parameters:
cookieString: string - Raw Cookie header value
key: string (optional) - Cookie name (default: "kuzenbo-theme")
Returns: ThemePreference | null
parseThemePreference
Validate and parse theme preference string.
import { parseThemePreference } from "@kuzenbo/theme";
const theme1 = parseThemePreference("dark"); // "dark"
const theme2 = parseThemePreference("invalid"); // null
Parameters:
value: string | null | undefined
Returns: ThemePreference | null
resolveDefaultThemePreference
Resolve theme preference from setting and system preference.
import { resolveDefaultThemePreference } from "@kuzenbo/theme";
const theme1 = resolveDefaultThemePreference("dark", "system");
// Returns "dark" (uses system)
const theme2 = resolveDefaultThemePreference("dark", "light");
// Returns "light" (explicit setting)
Parameters:
systemTheme: ThemePreference - Detected system preference
defaultThemeSetting: ThemeSetting (optional) - User setting
Returns: ThemePreference
resolveThemeBootstrapPlan
Compute theme bootstrap strategy from multiple sources.
import { resolveThemeBootstrapPlan } from "@kuzenbo/theme";
const plan = resolveThemeBootstrapPlan({
cookieTheme: "dark",
storageTheme: null,
systemTheme: "light",
defaultThemeSetting: "system"
});
// {
// resolvedTheme: "dark",
// shouldWriteCookie: false,
// shouldWriteStorage: true
// }
Parameters:
input: ThemeBootstrapPlanInput
cookieTheme: ThemePreference | null
storageTheme: ThemePreference | null
systemTheme: ThemePreference
defaultThemeSetting: ThemeSetting (optional)
Returns: ThemeBootstrapPlan
resolvedTheme: ThemePreference - Final theme to apply
shouldWriteCookie: boolean - Whether to persist to cookie
shouldWriteStorage: boolean - Whether to persist to localStorage
Constants
Theme Keys
import {
THEME_COOKIE_KEY,
THEME_STORAGE_KEY,
DEFAULT_THEME_SETTING,
THEME_COOKIE_MAX_AGE_SECONDS,
SYSTEM_DARK_MEDIA_QUERY
} from "@kuzenbo/theme";
console.log(THEME_COOKIE_KEY); // "kuzenbo-theme"
console.log(THEME_STORAGE_KEY); // "kuzenbo-theme"
console.log(DEFAULT_THEME_SETTING); // "system"
console.log(THEME_COOKIE_MAX_AGE_SECONDS); // 31536000 (1 year)
console.log(SYSTEM_DARK_MEDIA_QUERY); // "(prefers-color-scheme: dark)"
TypeScript Interfaces
ThemePreference
type ThemePreference = "dark" | "light";
ThemeSetting
type ThemeSetting = ThemePreference | "system";
ThemeBootstrapPlan
interface ThemeBootstrapPlan {
resolvedTheme: ThemePreference;
shouldWriteCookie: boolean;
shouldWriteStorage: boolean;
}
interface ThemeBootstrapPlanInput {
cookieTheme: ThemePreference | null;
defaultThemeSetting?: ThemeSetting;
storageTheme: ThemePreference | null;
systemTheme: ThemePreference;
}
ThemeBootstrapScriptOptions
interface ThemeBootstrapScriptOptions {
defaultThemeSetting?: ThemeSetting;
}
CSS Variables
Kuzenbo themes use CSS custom properties defined in @kuzenbo/theme/default.css:
:root {
--kb-background: oklch(1 0 0);
--kb-foreground: oklch(0.141 0.005 285.823);
--kb-primary: oklch(0.21 0.006 285.885);
--kb-primary-foreground: oklch(0.985 0 0);
/* ... more color tokens */
}
.dark {
--kb-background: oklch(0.141 0.005 285.823);
--kb-foreground: oklch(0.985 0 0);
/* ... dark mode overrides */
}
Semantic Color Tokens
Use these tokens in your components:
--kb-background / --kb-foreground
--kb-primary / --kb-primary-foreground
--kb-secondary / --kb-secondary-foreground
--kb-muted / --kb-muted-foreground
--kb-accent / --kb-accent-foreground
--kb-card / --kb-card-foreground
--kb-popover / --kb-popover-foreground
--kb-border, --kb-input, --kb-ring
--kb-danger, --kb-warning, --kb-info, --kb-success
<div className="bg-background text-foreground border-border">
<button className="bg-primary text-primary-foreground">
Primary Action
</button>
</div>
Complete Example
// app/layout.tsx
import { ThemeProvider, ThemeBootstrapScript } from "@kuzenbo/theme";
import { KuzenboProvider } from "@kuzenbo/core/provider";
import "@kuzenbo/theme/default.css";
export default function RootLayout({ children }) {
return (
<html suppressHydrationWarning>
<head>
<ThemeBootstrapScript defaultThemeSetting="system" />
</head>
<body>
<ThemeProvider>
<KuzenboProvider defaultSize="md">
{children}
</KuzenboProvider>
</ThemeProvider>
</body>
</html>
);
}
// components/theme-toggle.tsx
import { useTheme } from "next-themes";
import { Button } from "@kuzenbo/core/ui/button";
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<Button
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
variant="outline"
>
{theme === "dark" ? "Light" : "Dark"} Mode
</Button>
);
}
Server-Side Rendering
For SSR frameworks, use the bootstrap script to prevent theme flash:
import { ThemeBootstrapScript } from "@kuzenbo/theme";
import { cookies } from "next/headers";
export default async function RootLayout({ children }) {
// Optional: Read initial theme from cookies
const cookieStore = await cookies();
const cookieString = cookieStore.toString();
return (
<html suppressHydrationWarning>
<head>
<ThemeBootstrapScript />
</head>
<body>{children}</body>
</html>
);
}
The script will:
- Check cookie for saved preference
- Check localStorage for saved preference
- Detect system preference via media query
- Apply theme to
<html> element before first paint
- Sync preferences across storage methods