# How to Integrate `next-themes` for Theme Support in Next.js 14

> Integrate next-themes for seamless theme support in Next.js 14. Wrap your layout with ThemeProvider and use the useTheme hook for dynamic theming. Learn how now.

- Repository: [Ege Chelebi/blog](https://github.com/woosal1337/blog)
- Tags: how-to-guide
- Published: 2026-08-06

---

**Integrate `next-themes` by wrapping your Next.js 14 root layout with the `ThemeProvider` configured with `attribute="class"` and `suppressHydrationWarning`, then consume theme state using the `useTheme` hook in client components.**

The `woosal1337/blog` repository demonstrates a production-ready pattern to integrate `next-themes` for theme support in Next.js 14 App Router applications. This implementation applies theme classes early during hydration to eliminate flash-of-unstyled-content (FOUC) and provides a type-safe API for toggling between light, dark, and system preferences.

## Install the Dependency

Ensure `next-themes` is listed in your [`package.json`](https://github.com/woosal1337/blog/blob/main/package.json) (the repository uses version `^0.3.0`):

```json
"next-themes": "^0.3.0"

```

Install it via your package manager if it is not already present:

```bash
npm install next-themes@^0.3.0

```

## Configure the ThemeProvider

### Create a Provider Wrapper

Create a client component at [`components/providers/theme-provider.tsx`](https://github.com/woosal1337/blog/blob/main/components/providers/theme-provider.tsx) that re-exports the `next-themes` provider. This isolates the `"use client"` directive, preventing your root layout from becoming a Client Component while preserving TypeScript types from `next-themes/dist/types`.

```tsx
// components/providers/theme-provider.tsx
"use client";

import { ThemeProvider as NextThemesProvider } from "next-themes";
import type { ThemeProviderProps } from "next-themes/dist/types";

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}

```

### Update the Root Layout

In [`app/layout.tsx`](https://github.com/woosal1337/blog/blob/main/app/layout.tsx), import the wrapper and wrap your application content. Add `suppressHydrationWarning` to the `<html>` tag to prevent hydration mismatch warnings, as the `attribute` value is set client-side before React hydrates. Lines 108-115 in the source layout configure the provider with project-specific defaults:

```tsx
// app/layout.tsx
import { ThemeProvider } from "@/components/providers/theme-provider";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html className="dark" suppressHydrationWarning>
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="dark"
          forcedTheme="dark"
          enableSystem={false}
          storageKey="chele.bi.theme"
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

```

This configuration forces a dark theme (`forcedTheme="dark"`), disables system theme detection (`enableSystem={false}`), and persists the preference under the localStorage key `chele.bi.theme`. The `disableTransitionOnChange` prop suppresses CSS transitions during theme switches to prevent jarring visual effects.

## Build a Theme Switcher UI

### Access Theme State with useTheme

Any client component can access the current theme and setter function via the `useTheme` hook exported by `next-themes`.

```tsx
"use client";

import { useTheme } from "next-themes";

export function ThemeDebug() {
  const { theme, setTheme } = useTheme();
  return <button onClick={() => setTheme("dark")}>Current: {theme}</button>;
}

```

### Create the Switcher Component

The repository includes a complete implementation at [`components/ds/theme-switcher.tsx`](https://github.com/woosal1337/blog/blob/main/components/ds/theme-switcher.tsx) that renders a radio-group interface. It handles the client-side mounting nuance—components must wait for mount to avoid hydration mismatches where server HTML differs from the client's initial render.

```tsx
// components/ds/theme-switcher.tsx
"use client";

import { cn } from "@/lib/utils";
import { useTheme } from "next-themes";
import * as React from "react";

const OPTIONS = [
  { value: "light", label: "Light" },
  { value: "dark",  label: "Dark" },
  { value: "system", label: "Auto" },
] as const;

export function ThemeSwitcher({ className }: { className?: string }) {
  const { theme, setTheme } = useTheme();
  const [mounted, setMounted] = React.useState(false);

  React.useEffect(() => setMounted(true), []);

  return (
    <div role="radiogroup" aria-label="Theme" className={cn("inline-flex items-center gap-0.5 rounded-pill border p-0.5", className)}>
      {OPTIONS.map(option => {
        const selected = mounted && theme === option.value;
        return (
          <button
            key={option.value}
            type="button"
            role="radio"
            aria-checked={selected}
            onClick={() => setTheme(option.value)}
            className={cn(
              "rounded-pill px-3 py-1 text-caption transition-colors duration-200",
              selected ? "bg-action text-white" : "text-muted-foreground hover:text-foreground"
            )}
          >
            {option.label}
          </button>
        );
      })}
    </div>
  );
}

```

The `mounted` check on line 21 ensures the component only evaluates `theme === option.value` after React has mounted on the client. This prevents hydration errors while still calling `setTheme` immediately when the user clicks a button.

## Summary

- **Install `next-themes`** (version `^0.3.0`) as a project dependency according to the [`package.json`](https://github.com/woosal1337/blog/blob/main/package.json) in `woosal1337/blog`.
- **Create a thin wrapper** in [`components/providers/theme-provider.tsx`](https://github.com/woosal1337/blog/blob/main/components/providers/theme-provider.tsx) to isolate the `"use client"` directive and forward props to `next-themes` with full type safety.
- **Configure the provider** in [`app/layout.tsx`](https://github.com/woosal1337/blog/blob/main/app/layout.tsx) with `suppressHydrationWarning` on the `<html>` element to prevent React hydration mismatches.
- **Set specific behavior** using props like `forcedTheme`, `storageKey="chele.bi.theme"`, and `disableTransitionOnChange` to control persistence and UI transitions.
- **Consume the theme** using the `useTheme` hook in client components, guarding against hydration mismatches by checking `mounted` state before rendering theme-dependent UI.

## Frequently Asked Questions

### How do I prevent hydration mismatches when using `next-themes`?

Add `suppressHydrationWarning` to the `<html>` tag in your root layout. For components that render UI elements differently based on the current theme (like active state indicators), initialize a `mounted` state variable to `false` and set it to `true` inside a `useEffect` hook. Only compare or display theme values after `mounted` is true to ensure server and client HTML align.

### Can I force a specific theme and disable system detection?

Yes. Pass `forcedTheme="dark"` (or `"light"`) and `enableSystem={false}` to the `ThemeProvider` props. As implemented in lines 108-115 of the `woosal1337/blog` source code at [`app/layout.tsx`](https://github.com/woosal1337/blog/blob/main/app/layout.tsx), this forces all users to view the site in dark mode regardless of their system preferences while still allowing the theme state to function internally.

### Where should I store the theme preference?

Use the `storageKey` prop on the `ThemeProvider` to specify a custom localStorage key, such as `storageKey="chele.bi.theme"`. This persists the user's selection across browser sessions. If omitted, `next-themes` defaults to the standard key, but explicit keys prevent collisions when running multiple apps on localhost.

### Why wrap the NextThemesProvider in a separate component?

Creating a wrapper component in [`components/providers/theme-provider.tsx`](https://github.com/woosal1337/blog/blob/main/components/providers/theme-provider.tsx) allows you to mark only that file as `"use client"` while keeping your root layout as a Server Component. This pattern maintains the performance benefits of React Server Components for the rest of your application while isolating client-side theme logic and avoiding "use client" pollution at the layout level.