- Published on
Implement Dark Mode in Next.js with Shadcn UI
- Authors
- Name
- Ripal & Zalak
Troubleshooting Dark Mode in Next.js with the App Directory
Adding dark mode functionality to your Next.js project using the new app directory structure can sometimes lead to unexpected issues. This guide provides step-by-step solutions to common problems encountered when using next-themes
and Shadcn UI components.
The Issue
In this scenario, the dark mode toggle button registers click events and logs changes in the console, but the theme does not update across components as expected. Additionally, the theme state remains undefined
in the useTheme
hook.
Debugging and Fixing the Problem
ThemeProvider
Wrapping
1. Incorrect Ensure the ThemeProvider
from next-themes
wraps your application correctly, including the {children}
prop.
Here’s the corrected RootLayout
implementation:
import './globals.css'
import { ThemeProvider } from '../components/theme-provider'
import ClientLayout from './components/ClientLayout'
import { cn } from '@/lib/utils'
import { Inter as FontSans } from 'next/font/google'
const fontSans = FontSans({
subsets: ['latin'],
variable: '--font-sans',
})
export default async function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body className={cn('bg-background min-h-screen font-sans antialiased', fontSans.variable)}>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<ClientLayout />
{children}
</ThemeProvider>
</body>
</html>
)
}
useTheme
Hook Integration
2. Verify Use the useTheme
hook properly to access and update the current theme. Ensure the ThemeProvider
is correctly initializing the theme context.
Here’s an updated ModeToggle
component:
'use client'
import { useTheme } from 'next-themes'
import { Moon, Sun } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
export function ModeToggle() {
const { theme, setTheme } = useTheme()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme('light')}>Light</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>Dark</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>System</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
3. Tailwind CSS Configuration
Ensure your tailwind.config.js
file supports dark mode. Use the class
strategy for toggling themes:
module.exports = {
darkMode: 'class',
content: ['./app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
}
4. Hydration Issue
If the theme state appears as undefined
, it may be a hydration mismatch issue. Suppress hydration warnings and verify the theme context initialization:
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
{children}
</ThemeProvider>
FAQs
useTheme().theme
returning undefined?
Why is This typically occurs when the ThemeProvider
is not properly wrapping the application. Verify that the ThemeProvider
is included at the top level of your layout and wraps all children components.
How do I disable transitions during theme changes?
Use the disableTransitionOnChange
prop in the ThemeProvider
to avoid unwanted animations:
<ThemeProvider disableTransitionOnChange />
How do I check the current theme?
Log the theme
value from useTheme()
in your component:
const { theme } = useTheme()
console.log('Current theme:', theme)
Key Takeaways
- Wrap your entire application with the
ThemeProvider
. - Use Tailwind CSS’s
darkMode: "class"
setting. - Suppress hydration warnings for seamless rendering.
- Test the
theme
state in theuseTheme
hook for proper initialization.
By following these steps, you can successfully implement dark mode in your Next.js project using the app directory and Shadcn UI components.