Animated Light/Dark Theme Switch
Use the View Transitions API for the now-iconic 'circle wipe' theme switch effect — a creative use of clip-path on the snapshot pseudo-elements.
Detailed Explanation
The Circle-Wipe Effect
Made famous by Chrome's own DevRel demos, this effect grows a circular reveal from the click position, transitioning the entire page from light to dark mode through a clean wipe.
async function toggleTheme(event) {
const x = event.clientX;
const y = event.clientY;
const transition = document.startViewTransition(() => {
document.documentElement.classList.toggle('dark');
});
await transition.ready;
const endRadius = Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y),
);
document.documentElement.animate(
{
clipPath: [
`circle(0px at ${x}px ${y}px)`,
`circle(${endRadius}px at ${x}px ${y}px)`,
],
},
{
duration: 500,
easing: 'ease-in-out',
pseudoElement: '::view-transition-new(root)',
},
);
}
How it works
startViewTransitioncaptures the light theme.- The class toggle switches to dark; React re-renders.
- The browser captures the dark theme.
await transition.readywaits until the snapshots are ready.- We animate
clip-pathon the::view-transition-new(root)pseudo-element — revealing the new (dark) snapshot through a growing circle while the old (light) snapshot stays visible underneath.
Direction reversal
For dark→light, animate the old pseudo-element shrinking instead of the new one growing. Or always animate the new one growing — it works equally well in both directions because the user perceives the change identically.
Reduced motion
if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
document.documentElement.classList.toggle('dark');
return;
}
Skip the animation entirely for users with the preference set.
Use Case
Theme switchers in marketing sites and dashboards where the visual flourish is part of the brand. Production examples: Chrome 111+ DevRel demos, several CSS Day talks, and Linear's settings panel.