Implement Theme Provider to Change Themes without Wrapping Children in Context in Next.js/React.Js
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:
Install Dependencies: Ensure you have the necessary dependencies installed, including
next-themes
for theme management andcookies-next
for cookies management.Create Theme Provider Component: Develop a custom
ThemeProvider
component that handles theme switching and cookie management.Integrate Theme Provider: Integrate the
ThemeProvider
component into your application layout to make sure client theming is synced with server on hydration.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:
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.
// 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:
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
.
"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.