A production-ready Next.js theme template demonstrating a correct and modern implementation of light and dark mode switching using the View Transitions API.
This repository is focused on theming architecture, not UI gimmicks. It serves as a reference implementation for smooth, predictable, and hydration-safe theme transitions in real-world applications.
Built with Next.js App Router, Tailwind CSS 4, shadcn/ui, and next-themes.
git clone https://github.com/Rjk-Jami/theme_changing_template.git
cd theme_changing_template
npm installnpm run devOpen:
http://localhost:3000
Most theme implementations suffer from:
- Flickering during hydration
- Abrupt or jarring theme changes
- Server/client theme mismatch
- Hard-to-maintain hacks
This template demonstrates:
- Correct class-based theming
- Single-button theme toggle
- Circular reveal transition originating from the toggle button
- Hydration-safe behavior
- Token-based styling using CSS variables
- Next.js App Router (Next.js 15 / React 19 ready)
- View Transitions API for theme switching
- Circular
clip-pathreveal animation - Tailwind CSS 4 with design tokens
- shadcn/ui component foundation
- Fully responsive
- Type-safe and production-oriented
app/
├─ layout.tsx # ThemeProvider + hydration handling
├─ page.tsx # One-page landing demo
└─ globals.css # Theme tokens + View Transition rules (critical)
components/
├─ Theme/
│ └─ theme-control.tsx # ModeToggle with circular reveal animation
├─ Layout/
│ ├─ Header.tsx
│ └─ Footer.tsx
└─ widgets/ # Optional demo widgetsThe transition uses the View Transitions API to animate between the old and new DOM snapshots.
Add the following to app/globals.css:
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
}
::view-transition-old(root) {
z-index: 1;
}
::view-transition-new(root) {
z-index: 9999;
}
::view-transition-image-pair(root) {
isolation: isolate;
}const toggleTheme = () => {
const endRadius = Math.hypot(
Math.max(x, window.innerWidth - x),
Math.max(y, window.innerHeight - y)
)
document.startViewTransition(() => {
setTheme(nextTheme)
}).ready.then(() => {
document.documentElement.animate(
[
{ clipPath: `circle(0px at ${x}px ${y}px)` },
{ clipPath: `circle(${endRadius}px at ${x}px ${y}px)` },
],
{
duration: 800,
easing: "cubic-bezier(.2,.8,.2,1)",
pseudoElement: "::view-transition-new(root)",
}
)
})
}<html lang="en" suppressHydrationWarning>Widgets are included only to demonstrate theme consistency.
npx shadcn@latest add card- Chrome / Edge / Arc — Supported
- Safari — Partial
- Firefox — Not supported
Created by KhanDevs