Tailwind Animation Utilities Playground

by tailkits

HTML

<!-- Head bits go here because JSFiddle merges into one page -->
<head>
  <!-- Tailwind v4 browser CDN -->
  <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

  <!-- Enter/exit utilities -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tw-animate.css">
</head>

<body class="antialiased bg-white text-slate-800">
  <!-- Safelist classes used by JS so the CDN compiles them -->
  <div class="hidden
    animate-spin animate-ping animate-pulse animate-bounce
    animate-in animate-out fade-in fade-out zoom-in-95 zoom-out-95
    slide-in-from-top-4 slide-out-to-top-4
    duration-150 duration-300 duration-500 duration-700 duration-1000
    delay-0 delay-150 delay-300 delay-500 delay-700 delay-1000
    ease-linear ease-in ease-out ease-in-out
    motion-reduce:animate-none
  "></div>

  <div class="mx-auto max-w-4xl p-4 md:p-6">
    <header class="mb-4 md:mb-6">
      <h1 class="text-xl md:text-2xl font-semibold">Tailwind Animation Utilities Playground</h1>
      <p class="text-sm text-slate-600">Built-ins, enter/exit, custom keyframes · tweak duration, delay, easing</p>
    </header>

    <section class="grid grid-cols-1 md:grid-cols-3 gap-4 md:gap-6">
      <!-- Built-ins -->
      <div class="rounded-2xl border p-4">
        <h2 class="text-base font-medium mb-2">Built-ins</h2>
        <div class="flex flex-wrap gap-2">
          <button data-anim="animate-spin" class="px-3 py-1.5 rounded bg-slate-900 text-white">Spin</button>
          <button data-anim="animate-ping" class="px-3 py-1.5 rounded bg-slate-900 text-white">Ping</button>
          <button data-anim="animate-pulse" class="px-3 py-1.5 rounded bg-slate-900 text-white">Pulse</button>
          <button data-anim="animate-bounce" class="px-3 py-1.5 rounded bg-slate-900 text-white">Bounce</button>
        </div>
      </div>

      <!-- Enter/Exit -->
      <div class="rounded-2xl border p-4">
        <h2...

CSS

/* Custom keyframes + class (duration driven by CSS var) */
@keyframes wiggle { 0%,100% { transform: rotate(-3deg) } 50% { transform: rotate(3deg) } }
.animate-wiggle { animation: wiggle var(--tkw-duration, 600ms) ease-in-out 1; }

JavaScript

const thing = document.getElementById("thing");
const builtInButtons = document.querySelectorAll("[data-anim]");
const btnShow = document.getElementById("btn-show");
const btnHide = document.getElementById("btn-hide");
const btnWiggle = document.getElementById("btn-wiggle");
const durSlider = document.getElementById("dur");
const resetBtn = document.getElementById("reset");
const durationSel = document.getElementById("duration");
const delaySel = document.getElementById("delay");
const easeSel = document.getElementById("ease");

function applyTiming(el) {
  el.className = el.className
    .split(" ")
    .filter(c => !/^duration-|^delay-|^ease-/.test(c))
    .join(" ");
  el.classList.add(durationSel.value, delaySel.value, easeSel.value);
}

function clearAnimClasses() {
  thing.className = thing.className
    .split(" ")
    .filter(c => !/^animate-|^fade-|^zoom-|^slide-/.test(c))
    .join(" ");
}

// Built-ins
builtInButtons.forEach(btn => {
  btn.addEventListener("click", () => {
    clearAnimClasses();
    thing.classList.add(btn.dataset.anim);
    applyTiming(thing);
  });
});

// Enter
btnShow.addEventListener("click", () => {
  clearAnimClasses();
  thing.classList.remove("hidden");
  thing.classList.add("animate-in","fade-in","zoom-in-95","slide-in-from-top-4");
  applyTiming(thing);
});

// Exit
btnHide.addEventListener("click", () => {
  clearAnimClasses();
  thing.classList.add("animate-out","fade-out","zoom-out-95","slide-out-to-top-4");
  applyTiming(thing);
  thing.addEventListener("animationend", () => {
    thing.classList.add("hidden");
    clearAnimClasses();
  }, { once: true });
});

// Custom keyframe
btnWiggle.addEventListener("click", () => {
  clearAnimClasses();
  thing.style.setProperty("--tkw-duration", durSlider.value + "ms");
  thing.classList.add("animate-wiggle");
});

// Timing controls
durationSel.addEventListener("change", () =>...