A simple but powerful dark mode solution for Next.js with smooth theme transitions
- 🌗 Support for dark, light, and system theme modes
- 🎭 Multiple transition effects
- Fade transition (default)
- Circular reveal from element or coordinates
- Custom animations with Web Animations API
- 🚀 Based on next-themes and View Transitions API
- 🖥️ Server-side rendering (SSR) compatible
- ⚡ Prevents flash of unstyled content (FOUC)
- 🔄 Graceful fallback for unsupported browsers
npm install next-themes next-easy-darkmode
# or
yarn add next-themes next-easy-darkmode
# or
pnpm add next-themes next-easy-darkmode
# or
bun add next-themes next-easy-darkmode
- Create a theme provider:
// components/theme-provider.tsx
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({
children,
...props
}: React.ComponentProps<typeof NextThemesProvider>) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
- Wrap your root layout:
// app/layout.tsx
import { ThemeProvider } from "@/components/theme-provider"
export default function RootLayout({ children }: RootLayoutProps) {
return (
<html lang="en" suppressHydrationWarning>
<head />
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
)
}
- Use the hook in your components:
import { useDarkMode } from 'next-easy-darkmode'
const MyComponent = () => {
const { toggle } = useDarkMode()
return (
<button onClick={toggle}>
Toggle
</button>
)
}
Instant theme switching without any transition effect:
const { toggle } = useDarkMode({
type: 'none'
})
Simple fade transition between light and dark mode:
// These are equivalent
const { toggle } = useDarkMode()
const { toggle } = useDarkMode({ type: 'default' })
Circular reveal transition that expands from a point:
// Reveal from button center
const MyComponent = () => {
const buttonRef = useRef<HTMLButtonElement>(null)
const { toggle } = useDarkMode({
type: 'circular-reveal',
center: { ref: buttonRef },
duration: 300,
easing: 'ease-out'
})
return (
<button ref={buttonRef} onClick={toggle}>
Toggle
</button>
)
}
// Reveal from specific coordinates
const { toggle } = useDarkMode({
type: 'circular-reveal',
center: { x: 100, y: 100 },
duration: 500,
easing: 'ease-in-out'
})
Full control over transitions using Web Animations API:
// Slide from top
const { toggle } = useDarkMode({
type: 'custom',
new: {
keyframes: [
{ transform: 'translateY(-100%)' },
{ transform: 'translateY(0)' }
],
options: {
duration: 300,
easing: 'ease-out'
}
}
})
See More Examples or Live Demo.
The smooth transitions require the View Transitions API. For browsers that don't support it, the theme will change instantly without transition effects.
Browser | Version | Global Support |
---|---|---|
Chrome | ✅ 111+ | 84.72% |
Edge | ✅ 111+ | 4.82% |
Firefox | ❌ Not supported | - |
Safari | ✅ 18.0+ | 2.65% |
Opera | ✅ 97+ | 2.37% |
Android Chrome | ✅ 111+ | 42.13% |
iOS Safari | ✅ 18.0+ | 18.62% |
Samsung Internet | ✅ 23+ | 2.84% |
Data from Can I Use (2025)
Contributions are welcome! Please feel free to submit a Pull Request.