webdevpuneet.com

AutoAnimate Todo List — FLIP Technique Explained

AutoAnimate Todo List · Dashboards · Plain HTML & CSS · Live preview

CategoryDashboards

Copy & paste

Source Code

<div class="aat-stage">
  <div class="aat-head">
    <span class="aat-tag">@formkit/auto-animate · one call, zero orchestration</span>
    <h2>Today's Tasks</h2>
  </div>
  <form class="aat-form" id="aatForm">
    <input class="aat-input" id="aatInput" type="text" placeholder="Add a task…" autocomplete="off" />
    <button class="aat-add" type="submit">Add</button>
  </form>
  <ul class="aat-list" id="aatList">
    <li class="aat-item" data-id="1"><label><input type="checkbox" /><span>Write the quarterly report</span></label><button class="aat-del" aria-label="Delete">✕</button></li>
    <li class="aat-item" data-id="2"><label><input type="checkbox" /><span>Review pull requests</span></label><button class="aat-del" aria-label="Delete">✕</button></li>
    <li class="aat-item" data-id="3"><label><input type="checkbox" /><span>Book flights for offsite</span></label><button class="aat-del" aria-label="Delete">✕</button></li>
    <li class="aat-item is-done" data-id="4"><label><input type="checkbox" checked /><span>Reply to design feedback</span></label><button class="aat-del" aria-label="Delete">✕</button></li>
  </ul>
</div>
<script type="module">
import autoAnimate from 'https://cdn.jsdelivr.net/npm/@formkit/[email protected]/index.mjs';

var list = document.getElementById('aatList');

// The entire animation system is this one call. autoAnimate watches the
// parent element with a MutationObserver; whenever a direct child is added,
// removed, or reordered by ANY future code — this file's, or code you add
// later — it automatically animates the change. No callback, no per-action
// wiring, ever needed again below this line.
autoAnimate(list);

var nextId = 5;

document.getElementById('aatForm').addEventListener('submit', function (e) {
  e.preventDefault();
  var input = document.getElementById('aatInput');
  var text = input.value.trim();
  if (!text) return;

  var li = document.createElement('li');
  li.className = 'aat-item';
  li.dataset.id = String(nextId++);
  li.innerHTML = '<label><input type="checkbox" /><span></span></label><button class="aat-del" aria-label="Delete">✕</button>';
  li.querySelector('span').textContent = text;
  list.appendChild(li);

  input.value = '';
});

list.addEventListener('click', function (e) {
  if (e.target.matches('.aat-del')) {
    e.target.closest('.aat-item').remove();
    return;
  }
  if (e.target.matches('input[type="checkbox"]')) {
    var item = e.target.closest('.aat-item');
    item.classList.toggle('is-done', e.target.checked);
    // Moving the completed item to the end of the list is a plain DOM
    // mutation (appendChild on an existing node moves it) — autoAnimate
    // treats this reorder exactly like an add or remove and animates it.
    if (e.target.checked) list.appendChild(item);
  }
});
</script>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

One-call setup
autoAnimate(list) is the entire animation system — no per-action animation code anywhere else.
FLIP-based positioning
Reorders animate via measured before/after bounding boxes and an inverted transform, not layout-property transitions.
MutationObserver-driven
A native browser API detects DOM changes, so any future code touching the list is covered automatically.
Add, remove, and reorder all covered
The same mechanism animates every kind of direct-child mutation with zero special-casing.
Completing is just a DOM move
Checking a task off calls plain appendChild to relocate it — no bespoke animation call.
Zero animation dependencies wired manually
No transition classes, no per-element event listeners for enter/exit states.
Framework-agnostic mutation source
Works the same whether the DOM change comes from this vanilla JS or any other code touching the container.
Accessible checkbox-driven state
Completion is a real checkbox input, keeping the list usable without JavaScript-only interaction.

About this UI Snippet

AutoAnimate Todo List — What FLIP Is and Why One Call Covers Every Future Mutation

Screenshot of the AutoAnimate Todo List snippet rendered live

Most "animate my list" solutions require wrapping every mutation — every add, remove, and reorder gets its own explicit before/after animation call. @formkit/auto-animate takes a fundamentally different approach: you call autoAnimate(parentElement) once, and it transparently animates *any* direct-child DOM mutation that happens afterward, forever, without you writing a single animation call anywhere else. This snippet's entire animation code is one line: autoAnimate(list).

The FLIP technique, in plain terms

FLIP stands for First, Last, Invert, Play, and it's the standard technique for animating layout changes that can't be expressed as a simple CSS transition (like a list item reordering, which changes an element's actual position in the DOM/layout, not just a style property). The steps:

1. First — record every affected element's current bounding box (position, size) before the mutation happens. 2. Last — let the mutation actually happen (the DOM add/remove/reorder), then immediately record each element's *new* bounding box. 3. Invert — for each element, compute the delta between First and Last, and apply an inverse CSS transform so the element is instantly repositioned to *look* exactly like it did before the mutation, even though it's now actually in its new DOM position. 4. Play — remove the inverse transform with a CSS transition, letting the element animate smoothly from its old apparent position to its real new one.

The trick is that steps 1-3 all happen synchronously with no visible flash, so the only thing the user perceives is step 4: a smooth animated transition from old position to new. autoAnimate implements exactly this loop internally.

Why one autoAnimate() call is enough for arbitrary future mutations

js autoAnimate(list);

This one call attaches a MutationObserver to list. A MutationObserver is a native browser API that fires a callback whenever a DOM subtree changes — child added, removed, reordered, or an attribute/text changed. Because autoAnimate hooks the observer once at the parent level rather than instrumenting individual operations, it doesn't matter *how* a child gets added, removed, or moved later — whether it's this snippet's appendChild, a completely different piece of code added six months from now, or a totally different framework's rendering pass touching the same DOM node. Any of those trigger the same observer callback, which runs the same FLIP measurement-and-transform sequence automatically. You never call an animation function directly; you only ever mutate the DOM normally, and autoAnimate intercepts the *effect* of that mutation.

Completing a task is a reorder, not a special case

js if (e.target.checked) list.appendChild(item);

Calling appendChild on a node that's already in the document doesn't clone it — it *moves* it to the end of its new parent (which, since it's the same parent here, is just a reorder to the last position). This is a completely ordinary DOM operation with zero autoAnimate-specific code, and autoAnimate animates it exactly the same way it animates an add or a delete: it's just another mutation the observer sees.

Reusing it

Anything you'd build with plain DOM mutation — a Kanban column, a leaderboard whose rows re-sort, a notification tray — gets List-animation for free the moment its container is wrapped in autoAnimate(). There's no API to learn beyond that one function call.

Step by step

How to Use

  1. 1
    No CDN script tag needed@formkit/auto-animate ships no UMD/global build — the demo imports it directly as an ES module inside a <script type="module"> tag instead.
  2. 2
    Paste HTML, CSS, and JSA todo list renders with a few starter tasks, one already checked off.
  3. 3
    Add a taskType into the input and submit — the new item animates in at the bottom.
  4. 4
    Check a task offIt strikes through, dims, and smoothly slides to the end of the list — a plain appendChild move.
  5. 5
    Delete a taskThe remaining items smoothly slide up to fill the gap, with no manual animation code for that either.
  6. 6
    Read the one setup lineautoAnimate(list) is the entire animation system — every future mutation is covered automatically.

Real-world uses

Common Use Cases

Task lists with animated changes
Add, complete and delete items with one autoAnimate(list) call, with no explicit animation code around each mutation.
Kanban cards moving columns
Animate cards when they move between lists, because a DOM move is just another mutation the MutationObserver notices.
Filtered and sorted lists
Let rows glide to their new positions when the order changes, using measured before and after bounding boxes in the FLIP technique.
Notification trays
Slide new alerts in and dismissed ones out without wiring individual transitions, so future code changes animate automatically.
Learning FLIP in practice
Use it as a minimal reference for first-last-invert-play, where the library does the measuring so you only change the DOM.

Got questions?

Frequently Asked Questions

First, Last, Invert, Play. First and Last record an element's bounding box before and after a DOM mutation. Invert applies a CSS transform that makes the element instantly LOOK like it's still in its old position even though it has actually moved. Play removes that transform with a CSS transition, so the element visibly animates from the old apparent position to its real new one.

autoAnimate attaches a native MutationObserver to the parent element, which is a browser API that fires whenever the DOM subtree changes — regardless of what caused the change. Because the observer watches the PARENT, not specific operations, any code that later adds, removes, or reorders a direct child of that parent triggers the same observer callback and gets the same FLIP animation, with no additional setup.

Calling appendChild on a DOM node that already exists in the document does not clone it — it relocates the existing node to the end of its parent. Since autoAnimate is already observing that parent for any child mutation, this ordinary move is automatically detected and animated with the exact same FLIP sequence as an add or delete, with zero autoAnimate-specific API used.

Yes — since it operates on the actual DOM via a MutationObserver rather than intercepting your code's API calls, it doesn't care whether the mutations come from vanilla DOM methods, React's reconciler, or Vue's renderer. You still only need to call autoAnimate() once on the parent element (typically via a ref in React or a directive in Vue).

Your plain DOM calls (appendChild, remove(), insertBefore, etc.) are what CHANGE the DOM; autoAnimate's MutationObserver callback is what OBSERVES that change and runs the FLIP sequence in response. You never call an animation function yourself — you just mutate the DOM as you normally would, and the observer does the rest.

No — autoAnimate applies its own inline transform and transition styles temporarily during the FLIP sequence and removes them afterward. You don't need to define enter/exit CSS classes the way you would with a typical transition-group style animation library.

@formkit/auto-animate publishes no UMD/IIFE build on npm — only an ES module (index.mjs) and its minified ESM equivalent, both ending in a real export statement, which throws a syntax error if loaded as a classic script. The fix is to import it directly, which requires the importing <script> tag itself to be type="module" — that's why this demo's whole script lives inline in the HTML instead of a separate CDN <script src> plus a global function.