Change Themes in Next.js 14 without Wrapping Children in Context .
Tags: nextjs, javascript, webdev, tailwindcss
Published: 2024-04-10 11:51:15

## Introduction

In Web applications, theming plays a crucial role in providing a personalised and visually appealing user experience. One common approach is to use a `ThemeProvider` Context to manage themes across the application. There is no problem with application's which is completely client side. This blog is specific to developer who is looking for a solution where there application is combination of client and server components and theming depends on `color-schema` or `className`.

With the release of Next.js 14+, wrapping children in a Context will make children a client component. In this blog post, we'll explore how to implement a ThemeProvider which will help us to avoid wrapping complete application into a Context provider.

## Implementing Theme Provider in Next.js 14+

To implement the Theme Provider without wrapping children in Context, we'll follow these steps:

1. **Install Dependencies**: Ensure you have the necessary dependencies installed, including `next-themes` for theme management and `cookies-next` for cookies management.

2. **Create Theme Provider Component**: Develop a custom `ThemeProvider` component that handles theme switching and cookie management.

3. **Integrate Theme Provider**: Integrate the `ThemeProvider` component into your application layout to make sure client theming is synced with server on hydration.

4. **Use Theme Switcher** : Integrate the `ThemeProvider` component into your application to switch theme.

Let's dive deeper into each step.

## Step 1: Install Dependencies

First, make sure you have `next-themes` installed in your Next.js project. If not, you can install it using npm or yarn:

```bash
npm install next-themes cookies-next
# or
yarn add next-themes cookies-next
```

## Step 2: Create Theme Provider Component

In your project, create a custom `ThemeProvider` component that leverages `next-themes` for theme management. This component will handle theme switching and cookie management for server-side rendering.

```jsx
// components/context/theme.tsx
import { setCookie } from "cookies-next";
import { ThemeProvider, useTheme } from "next-themes";
import type { ThemeProviderProps } from "next-themes/dist/types";
import { useEffect } from "react";

// Application theme provider
function AppThemeProvider({ children, ...props }: ThemeProviderProps) {
  return (
    <ThemeProvider enableColorScheme {...props}>
      <AppThemeProviderHelper />
      {children}
    </ThemeProvider>
  );
}

// Helper component to set theme in cookie
function AppThemeProviderHelper() {
  const { theme } = useTheme();

  useEffect(() => {
    setCookie("__theme__", theme, {
      expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
      path: "/",
    });
  }, [theme]);

  return null;
}

export default AppThemeProvider;
```

## Step 3: Integrate Theme Provider

Now, integrate the `ThemeProvider` component into your application layout in your `app/layout.tsx` file or any child layout as per requirement:

```jsx
import { MonaSans_Font } from "@/assets/fonts/MonaSans";
import dynamic from "next/dynamic";

const AppThemeProvider = dynamic(() => import("@/components/context/theme"), {
  ssr: false,
});

export default async function AppLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const theme = cookies().get("__theme__")?.value || "system";

  return (
    <html
      className={theme}
      lang="en"
      style={theme !== "system" ? { colorScheme: theme } : {}}
    >
      <body className={`${MonaSans_Font.className} flex flex-col h-full w-full overflow-hidden`}>
        {children}

        <AppThemeProvider 
           attribute="class" 
           defaultTheme={theme} 
           enableSystem 
        />
      </body>
    </html>
  );
}
```

## Step 3: Integrate Theme Provider in Theme Switching

A Tab's based switcher can be created as below to switch theme Using `AppThemeProvider`.


```jsx
"use client";

import AppThemeProvider from "@/components/context/theme";
import { Tabs, TabsList, TabsTrigger } from "ui/components/tabs";
import { useTheme } from "next-themes";

function Tab() {
  const { setTheme, theme } = useTheme();
  return (
    <Tabs className="w-full" onValueChange={setTheme} value={theme}>
      <TabsList className="grid w-full grid-cols-3">
        <TabsTrigger value="light">Light</TabsTrigger>
        <TabsTrigger value="dark">Dark</TabsTrigger>
        <TabsTrigger value="system">System</TabsTrigger>
      </TabsList>
    </Tabs>
  );
}

function ThemeTabs() {
  return (
    <AppThemeProvider attribute="class" defaultTheme="system" enableSystem>
      <Tab />
    </AppThemeProvider>
  );
}

export default ThemeTabs;

```


## Conclusion

Implementing a Theme Provider in Next.js 14+ without wrapping children in Context offers a cleaner and more efficient way to manage themes in your application without making complete application a client component.