Creating a Smooth Theme Toggle Animation in React/Next.js

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:

  1. Capture current state: The browser takes a snapshot of your page
  2. Execute updates: Your code runs to change the page state
  3. Capture new state: Another snapshot is taken after updates
  4. Create pseudo-elements: Both snapshots load into special CSS pseudo-elements
  5. Animate transition: CSS animations blend between the old and new states
  6. 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.