Skip to content

Next.js

@theme-kit/next

SSRRSCZero-flash

App Router theming with SSR-safe hydration, cookie persistence, and zero flash of incorrect theme.

Installation

Install the package for your framework alongside @theme-kit/core.

bash
pnpm add @theme-kit/next

Quick Start

Start from scratch — install the package, then wrap your app in the provider at the entry point shown below.

app/layout.tsx
tsx
1// app/layout.tsx
2import { ThemeProvider } from "@theme-kit/next";
3import { themes } from "./theme/themes";
4
5export default function RootLayout({ children }) {
6  return (
7    <ThemeProvider
8      themes={themes}
9      defaultTheme="mint-light"
10    >
11      {children}
12    </ThemeProvider>
13  );
14}
Every integration re-exports the full @theme-kit/core surface, so history, batching, snapshots, packs and lifecycle work the same way across frameworks.

Implementation

Read and update theme state from any component using the framework-native primitives below.

app/layout.tsx
tsx
1// app/layout.tsx
2import { ThemeProvider } from "@theme-kit/next";
3import { themes } from "./theme/themes";
4
5export default function RootLayout({ children }) {
6  return (
7    <ThemeProvider themes={themes} defaultTheme="mint-light">
8      {children}
9    </ThemeProvider>
10  );
11}
12
13// components/ThemeSwitcher.tsx
14"use client";
15import { useTheme } from "@theme-kit/next/client";

What's Available

@theme-kit/next ships 13 exports in 4 categories. Everything below is also documented in the full API reference. Click any export to reveal what it does and how to use it.

Server

Client

Transition

Scrollbar

Use Cases

The important features in practice — copy any of these straight into your app.

SSR provider with zero flash

The server component resolves the initial theme, renders CSS variables in the HTML, and blocks the flash.

SSR provider with zero flash
tsx
1// app/layout.tsx
2import { ThemeProvider } from "@theme-kit/next";
3
4export default function RootLayout({ children }) {
5  return (
6    <ThemeProvider
7      themes={themes}
8      defaultTheme="system"
9      className="scroll-smooth"
10      body={{ className: "font-sans" }}
11    >
12      {children}
13    </ThemeProvider>
14  );
15}

Client-side switcher

Hooks live behind @theme-kit/next/client and update cookies + DOM together.

Client-side switcher
tsx
1// app/theme-switcher.tsx
2"use client";
3import { useTheme } from "@theme-kit/next/client";
4
5export function ThemeSwitcher() {
6  const { theme, mode, setMode, toggleTheme } = useTheme();
7  return (
8    <button onClick={toggleTheme}>
9      {theme.name} · {mode}
10    </button>
11  );
12}

Scoped theming in a client component

ThemeScope works in any client component, next to server-rendered content.

Scoped theming in a client component
tsx
1// app/widgets.tsx
2"use client";
3import { ThemeScope, type ThemeTransitionOptions } from "@theme-kit/next/client";
4
5const transition: ThemeTransitionOptions = { duration: 300, easing: "ease" };
6
7export function Widget() {
8  return (
9    <ThemeScope theme="forest" transition={transition}>
10      <div className="rounded-xl p-4">Always forest here</div>
11    </ThemeScope>
12  );
13}

Scheduled + multi-window sync

Every runtime capability is available to client components through hooks.

Scheduled + multi-window sync
tsx
1// app/runtime-demo.tsx
2"use client";
3import { useEffect } from "react";
4import { useThemeRuntime, useThemeLifecycle } from "@theme-kit/next/client";
5
6export function RuntimeDemo() {
7  const runtime = useThemeRuntime();
8  const { on } = useThemeLifecycle();
9
10  useEffect(() => {
11    const off = on("beforeThemeChange", (e) => console.log(e.next.name));
12    return off;
13  }, [on]);
14
15  return (
16    <button onClick={() => runtime.update({ radius: { sm: 8, md: 12 } })}>
17      Soften corners
18    </button>
19  );
20}

Smooth theme transitions

Enable CSS transitions on theme changes for a polished user experience.

Smooth theme transitions
tsx
1// app/layout.tsx
2import { ThemeProvider } from "@theme-kit/next";
3
4export default function RootLayout({ children }) {
5  return (
6    <ThemeProvider
7      themes={themes}
8      defaultTheme="light"
9      transition={{
10        enabled: true,
11        duration: 300,
12        easing: "ease-in-out",
13      }}
14    >
15      {children}
16    </ThemeProvider>
17  );
18}

More Examples

Scoped theming, history controls, and framework-specific patterns.

layout + theme-switcher
tsx
1// app/layout.tsx
2import { ThemeProvider } from "@theme-kit/next";
3
4export default function RootLayout({ children }) {
5  return (
6    <ThemeProvider themes={themes} defaultTheme="mint-light"> {/* mint-light | mint-dark | light | dark | system */}
7      {children}
8    </ThemeProvider>
9  );
10}
11
12// app/theme-switcher.tsx
13"use client";
14import { useTheme } from "@theme-kit/next/client";
15
16export function ThemeSwitcher() {
17  const { theme, mode, setMode, toggleTheme } = useTheme();
18  return (
19    <div>
20      <button onClick={toggleTheme}>{theme.name} · {mode}</button>
21      <button onClick={() => setMode("dark")}>Dark</button>
22      <button onClick={() => setMode("light")}>Light</button>
23    </div>
24  );
25}

API Reference

Server

ExportDescription
ThemeProvider (Server Component)Reads `theme-mode`, `theme-family`, `theme-fingerprint` cookies, validates the fingerprint, resolves the initial theme, and renders `<html data-theme>` with inline CSS variables before hydration.
Blocking bootstrap scriptEmits a blocking script in `<head>` that applies the persisted theme before first paint.
Dark-mode CSS fallbackEmits `@media (prefers-color-scheme: dark)` styles when the persisted mode is `system`.
createNextThemePersistence()Mirrors selection to cookies (`theme-mode`, `theme-family`, `theme-name`, `theme-fingerprint`) so the server renders the right theme next request.
scheduled propPass `scheduled={{ lightTheme, darkTheme }}` to the server ThemeProvider to enable sunrise/sunset switching app-wide. Coordinates are optional — each visitor's timezone is auto-detected on the client.

Client

ExportDescription
ClientThemeProviderCookie + localStorage persistence, fingerprint check, and `.dark` class sync after hydration.
@theme-kit/next/clientRe-exports every React hook plus ThemeScope, ThemeInspector and ThemeModeButton for client components.
ThemeBootstrapInjects SSR dark-mode CSS via `useServerInsertedHTML`.
useThemeSchedule()Reactive sunrise/sunset controller (via `@theme-kit/next/client`): `enabled`, `active`, `status`, `sunrise`, `sunset`, `nextTransition` plus `enable()`/`disable()`/`set()`.

Transition

ExportDescription
transition propBuilt-in runtime transition support. Configure duration/easing once on the provider; Theme Kit generates the transition styles at runtime, so applications do not need to maintain theme-transition rules in global CSS.
runtime.store.set(theme, { suppressTransition: true })Per-update escape hatch from `@theme-kit/next/client`: call `runtime.store.set(theme, { suppressTransition: true })` when a switch must be instantaneous.

Scrollbar

ExportDescription
scrollbar propEnables Theme Kit's custom overlay scrollbar. The SSR bootstrap hides the native scrollbar before first paint, then the overlay engine synchronizes with browser scrolling without replacing native scroll behavior.
scrollbar optionConfigure the overlay appearance from semantic tokens, including an explicit color token when needed. The overlay remains theme-aware and updates with runtime theme changes.
Next.js — Theme Kit