Creating a Smooth Theme Toggle Animation in React/Next.js
Have you ever come across a website with a stunning dark mode transition where the theme appears to grow organically from the toggle switch? This elegant effect creates a truly delightful user experience. Let's build this modern interaction for your React or Next.js application using the View Transitions API.
Understanding the View Transitions API
Before diving into code, let's understand how this browser feature works:
- Capture current state: The browser takes a snapshot of your page
- Execute updates: Your code runs to change the page state
- Capture new state: Another snapshot is taken after updates
- Create pseudo-elements: Both snapshots load into special CSS pseudo-elements
- Animate transition: CSS animations blend between the old and new states
- Render result: Once complete, the browser displays the updated page
The beauty of this approach is that the browser handles the heavy lifting of creating smooth transitions between states.
Setting Up the Basic Toggle
Let's start with a simple dark mode toggle in React:
This works, but there's no animation. Let's fix that.
Adding View Transitions
To use View Transitions, wrap your state update in document.startViewTransition():
But there's a problem: React updates the DOM asynchronously. The transition might start before React actually updates the DOM, causing the animation to fail.
The flushSync Solution
React provides flushSync() to force synchronous DOM updates. This is one of the rare cases where using it is recommended:
Now you'll see a default fade animation between themes. Nice, but we want something more impressive.
Creating the Circular Growth Animation
The key to this impressive animation is using clip-path to create a circular mask that grows from the toggle button. Here's how:
Step 1: Get Button Position
Use a ref to track the button's position:
Step 2: Calculate Circle Dimensions
We need to determine:
- The center point of the button
- The radius needed to cover the entire screen
Step 3: Animate the Clip Path
Apply the animation to the new state pseudo-element:
Step 4: Disable Default Animations
Add this CSS to remove the default fade:
Handling Edge Cases
Make your implementation robust by checking for browser support and user preferences:
Complete Implementation
Here's the full working component:
This animation technique works anywhere on your page, making it perfect for creating engaging user experiences in React and Next.js applications.