Tutorials

Add a GIF Reveal Animation to Your Dark Mode Toggle (View Transitions API)

Use the CSS View Transitions API and a GIF mask to reveal your new theme with a stylish animation when visitors switch between light and dark mode.

On this page 6 sections
  1. 01How it works
  2. 021. The toggle button
  3. 032. The CSS
  4. 043. The JavaScript
  5. 05Make it yours
  6. 06Browser support

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

html
<button class="cs-header__scheme-toggle" aria-label="Toggle Theme">
  Toggle Theme
</button>

2. The CSS

html
<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

html
<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_URL with your own
  • Change the speed: update --anim-seconds in the CSS and ANIM_SECONDS in 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.

Syed Imran Murtaza

Need something like this built?

I build custom WordPress plugins, websites and web apps. Tell me what you need.

Let’s talk
→keep reading

You might also like