On this page 6 sections
Switching between light and dark mode is already a nice touch. You can make it memorable by revealing the new theme through an animated GIF mask. Here's how, using the View Transitions API.
How it works
- CSS animates a mask. The new theme appears through the shape of your GIF as it grows.
- JavaScript catches the toggle click and wraps the theme change in
document.startViewTransition(), which makes the animation possible.
It also respects reduced motion. If a visitor prefers less animation, the effect becomes almost instant.
1. The toggle button
<button class="cs-header__scheme-toggle" aria-label="Toggle Theme">
Toggle Theme
</button>2. The CSS
<style>
:root {
--expo-in: cubic-bezier(0.7, 0, 0.84, 0);
--anim-seconds: 3s; /* Match with JS ANIM_SECONDS */
}
/* Main transition animation */
::view-transition-group(root) {
animation-timing-function: var(--expo-in);
}
::view-transition-new(root) {
mask: url('your-gif-url') center / 0 no-repeat;
animation: vt-mask-scale var(--anim-seconds) both;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: vt-mask-scale var(--anim-seconds) both;
}
@keyframes vt-mask-scale {
0% { mask-size: 0; }
10% { mask-size: 50vmax; }
90% { mask-size: 50vmax; }
100% { mask-size: 2000vmax; }
}
/* Accessibility: Reduced Motion */
@media (prefers-reduced-motion: reduce) {
::view-transition-new(root),
::view-transition-old(root),
.dark::view-transition-old(root) {
animation-duration: 0.01s;
}
}
</style>3. The JavaScript
<script>
(function () {
"use strict";
const TOGGLE_SELECTOR = ".cs-header__scheme-toggle";
const GIF_URL = "your-gif-url";
const ANIM_SECONDS = 3;
/* Dynamically set the GIF in the CSS mask */
const styleEl = document.createElement("style");
styleEl.textContent =
"::view-transition-new(root){mask:url('" + GIF_URL + "') center/0 no-repeat;animation:vt-mask-scale " + ANIM_SECONDS + "s both}";
document.head.appendChild(styleEl);
/* Skip if View Transitions are not supported */
if (!document.startViewTransition) return;
let wrapping = false;
/* Intercept clicks on the toggle */
document.addEventListener("click", function (e) {
const btn = e.target.closest(TOGGLE_SELECTOR);
if (!btn) return;
if (wrapping || e.__vtWrapped) return;
e.preventDefault();
e.stopPropagation();
wrapping = true;
document.startViewTransition(() => {
const evt = new MouseEvent("click", { bubbles: true, cancelable: true, composed: true });
Object.defineProperty(evt, "__vtWrapped", { value: true });
btn.dispatchEvent(evt);
}).finished.finally(() => {
wrapping = false;
});
}, true);
})();
</script>Make it yours
- Change the GIF: replace
GIF_URLwith your own - Change the speed: update
--anim-secondsin the CSS andANIM_SECONDSin the JS together - Try other shapes: SVG masks work too
Browser support
The View Transitions API works in Chromium browsers such as Chrome and Edge. Other browsers skip the animation and switch themes instantly, so nothing breaks.
That's it. Your theme toggle goes from a simple colour flip to a small moment people remember. Need help fitting it into your site? Message me.

Need something like this built?
I build custom WordPress plugins, websites and web apps. Tell me what you need.

