Seif Hassan
Seif Hassan

Reputation: 394

Handle theme in nextjs with clerk

I have a nextjs 13 application and I'm using Clerk for authentication. I'm trying to match the theme of the app to the ClerkProvider component. I used the useTheme hook from next-themes to get the resolvedTheme and assign the theme accordingly, but that required converting the layout.tsx into a client component which misses up with the metadata. I'm wondering if there is a better way to handle this.

Here is my code:

import "./globals.css";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { ClerkProvider } from "@clerk/nextjs";
import { ThemeProvider } from "@/components/theme-provider";
import { useTheme } from "next-themes";
import { dark } from "@clerk/themes";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const { resolvedTheme } = useTheme();
  return (
    <ClerkProvider
      appearance={{
        baseTheme: resolvedTheme === "dark" ? dark : undefined,
      }}
    >
      <html lang="en" suppressHydrationWarning>
        <body className={inter.className}>
          <ThemeProvider
            attribute="class"
            defaultTheme="system"
            enableSystem
            disableTransitionOnChange
          >
            {children}
          </ThemeProvider>
        </body>
      </html>
    </ClerkProvider>
  );
}

Upvotes: 2

Views: 3001

Answers (4)

alexxxxx
alexxxxx

Reputation: 1

well clerk components(SignIn, SignUp, UserButton) accpet apperence as props. you can pass theme to those components.

"use clients";
import { useTheme } from 'next-themes'
import { dark } from '@clerk/theme'
import { UserButton } from '@clerk/nextjs';

export default function Page() {
  const { theme } = useTheme();
  const apprence = useMemo(()=>{
     let result: ComponentProps<typeof UserButton>['apprence'];
     if(theme === 'dark') {
        result = {
           baseTheme: dark
        }
     }
     return result;
  }, [theme])
  return (
   <UserButton apprence={apprence} userProfileProps={{apprence}} />
  )
}

Upvotes: 0

nick
nick

Reputation: 110

Rather than setting it from Clerk → ThemeProvider, I suggest to do it the other way round.

As mentioned in the answer by Ahmad Mughal, you won't be able to set the appearance in the ClerkProvider globally, since it's expected to run only on the server.

You can however set the appearance on the level of Clerk components where you can use useTheme() (if you declare the page a client component with 'use client').

A page like this will render the appearance of the Clerk SignIn component according to the current theme from the ThemeProvider:

"use client";

import { SignIn } from "@clerk/nextjs";
import { dark } from "@clerk/themes";
import { useTheme } from "next-themes";

const PageWithSignIn = () => {
  const { currentTheme } = useTheme();

  return (
    <SignIn
      appearance={{
        baseTheme: currentTheme === "dark" ? dark : undefined,
      }}
    />
  );
};

export default Page;

Upvotes: 7

Ahmad Mughal
Ahmad Mughal

Reputation: 1

ClerkProvider requires to be work only inside of a Server Component so using it inside a client component just makes it complain about useRouter.

Upvotes: 0

Ahmed Abdelbaset
Ahmed Abdelbaset

Reputation: 4916

Move your client code into a client component with children prop:

"use client"
// ...
export function Providers({children}) {
  const { resolvedTheme } = useTheme();
  return <ClerkProvider
      appearance={{
        baseTheme: resolvedTheme === "dark" ? dark : undefined,
      }}
    >
      {children}
    </ClerkProvider>
}

Then render this component in your layout.tsx

export function RootLayout() {
  return <html>
    <head>...</head>
    <body>
      <Providers>
        ...
      </Providers>
    </body>
  </html>
}

Upvotes: 0

Related Questions