Animation Bake-Off & the fleet posture: anime.js · tailwindcss-animated · animate.css · Svelte flip · auto-animate
The definitive fleet pointer for animation decisions. Opens with THE FLEET POSTURE — the Captain's dual-adoption ruling (t_2c3ef21a): tailwindcss-animated is the UTILITY GRAMMAR, animate.css the KEYFRAME CATALOG, anime.js the JS orchestrator, Svelte native the free baseline, auto-animate the convenience tier. Below: the measurement history — identical patterns (sort FLIP, add/remove enter/exit with the leaveTo spike, stagger wave, spring physics, completion events, animate.css flourishes, tailwindcss-animated grammar demos incl. custom @theme keyframes) under the engines side by side, with the reduced-motion honesty panel and the per-job verdict table.
The fleet posture
DECIDED — all five adopted Captain ruling · decision recordThe bake-off below is history; the posture is current policy. The five tools are
NOT competitors — each is adopted for its strength: tailwindcss-animated = grammar, animate.css = vocabulary, anime.js = orchestration, Svelte native = free baseline, auto-animate = convenience tier. This page is the definitive fleet pointer for animation decisions: cite rows of this
table (like #fleet-posture → tailwindcss-animated) when instructing agents.
| Tool | Use it for | Why | Demo |
|---|---|---|---|
| anime.js 4.5 | JS-driven animation: reorders with control, springs, stagger, completion events | The Captain test-drove it: full orchestration control with real completion events. | see demo → |
| tailwindcss-animated | The UTILITY GRAMMAR: duration/delay/iteration/timing/fill/direction/composition classes over ANY keyframes | Tailwind v4-native; composes over custom @theme keyframes too. | see demo → |
| animate.css | The KEYFRAME CATALOG: ~97 ready-made effects usable directly or as ports | Frozen since 2020 but still the fastest vocabulary; also the only auto-RM member. | see demo → |
| Svelte native | Free reorder FLIP for keyed lists (animate:flip) and in:/out: transitions | 0 kB, framework-maintained; the fleet survey found 798 transition: uses and 0 animate:. | see demo → |
| @formkit/auto-animate | Zero-config watch-the-DOM convenience for prototypes/admin lists | Attach once, forget — the convenience tier. | see demo → |
Decision record: ruling on this card (t_2c3ef21a), from the Captain test-driving the bake-off
page: anime.js is the JS-driven engine for reorders with control, springs, stagger, and completion events;
tailwindcss-animated is adopted as the utility grammar (Tailwind v4-native classes over any keyframes, including
custom @theme ones); animate.css stays as the keyframe catalog (~97 effects, usable directly or as
ports — the equivalent keyframes remain trivially writable under its Hippocratic-2.1 flag); Svelte native stays
the free baseline for keyed-list FLIP and in:/out: transitions; @formkit/auto-animate stays the zero-config
convenience tier. Fleet mapping for the auto-animate patterns lives on the auto-animate page — not
duplicated here. Reduced motion: animate.css covers itself; every other tool is gated by app code — the one-block
global mitigation lives in src/app.css and is part of the adoption.
Pulse strength ride-along: the base animate.css pulse reads as too subtle on small badges — the flourish demo now also ships pulse-strong (scaled 1.0→1.35, animate.css-compatible naming).
How each engine answers: anime.js — nothing built in, the page gates it; animate.css — built-in media query (only RM-positive member; answers the real OS setting, our toggle gates its demos); auto-animate — mount-time check only; Svelte — nothing built in, we pass duration 0. The toggle flips the page gate for all four; flip your OS setting to see animate.css's native query fire.
1 · Sort / reorder canonical FLIP
Same seed, same two controls —
Watches the container's children — reorder detection is automatic, no per-frame code.
Svelte mutates state → flushSync() inside layout.update() — the keyed-each reorder lands inside the FLIP snapshot window.
animate:flip 0kBBuilt into the framework — one directive, zero bytes added. Fleet survey: 798 transition: uses, 0 animate:. Unmined gold.
2 · Add / remove with enter/exit ⚠️ spike verdict inside
Fast clicks: the anime.js column settles asynchronously by design — each add/remove plays as its own choreography beat and further clicks queue until it releases, so this column can lag the other two for a moment. Removal stops for all columns together at two rows.
Enter/exit are automatic — the ghost machinery you saw in the scrollbar saga.
Leave rows: library never resurrects detached nodes — the recipe keeps them mounted one beat, hidden + leaveTo, released on onComplete.
transition:fly The outro holds the element until the transition ends — enter/exit ownership is built in.
3 · Stagger wave anime.js unique
Grid shuffle with stagger(35, { from: "center" }) as the layout delay.
Ripple from the center outward — stagger() through layout.update params.
No stagger API — items animate simultaneously (formkit #44, open). Chaining delays manually defeats the point.
animate:flip has no delay param; per-item transitionDelay hacks fight the FLIP timer.
4 · Spring physics anime.js unique
Rapid presses: while the ball's spring is in flight the button is briefly disabled and presses during
that window are ignored — bounces: counts only completed bounces, so the count and the
animation always agree.
The ball: keyframed squash+bounce on a spring ease. The list: the SAME spring as the layout ease — physics flow through layout.update.
Fixed easing strings only; no physics, no velocity carry-over.
spring() pre-solves to a cubic-bezier — no mid-flight retargeting, no true second-order motion.
5 · Completion events auto-animate's fatal gap
Animate → then run cleanup. Press all three, watch how each engine tells you it finished.
Trigger cleanup →
Controller is a frozen {parent, enable, disable, isEnabled, destroy} — no promise, no callback. The fleet workaround is a duration-scaled setTimeout (this demo, honestly labeled).
Trigger cleanup →
Every animation/timeline is a Timer: .then(), onComplete, seek/cancel/restart — the full lifecycle auto-animate lacks.
Trigger cleanup →
No promise API — but outro end-callbacks exist via transition: functions (the tick/callback form), and outroend events fire on outgoing elements. Verified honestly: partial coverage, zero deps.
6 · animate.css flourishes 5.3kB CSS · 0 JS
Pure-CSS keyframes wired through a tiny class-toggle action with animationend cleanup — no stuck classes, no infinite loops. RM emulation collapses these too (via our gate — their built-in media query only answers the real OS setting).
attention seeker
validation error
one-shot entrance
dramatic exit (finite!)
Gotchas honored (scout §2.3): no .animate__infinite on these elements (infinite never fires animationend); classes removed after finish (fill-mode: both would otherwise leave ...Out elements frozen at opacity 0); re-trigger via #key remount.
Pulse-strong variant grammar over custom @theme keyframes
base pulse scale peak 1.13 → strong 1.35animate-pulse-strong (custom keyframe, app.css) 7 · tailwindcss-animated — the utility grammar grammar over any keyframes
ADOPTED as the fleet's UTILITY GRAMMAR (posture section ↑): Tailwind v4-native classes for duration, delay,
iteration, timing, fill, direction, and composition — over the plugin's predefined keyframes, over Tailwind's
own, and over custom @theme ones. Install is one import line, AFTER Tailwind:
@import "tailwindcss";
@import "tailwindcss-animated"; Grammar combo — entrance with delay, once
animate-jump-in animate-delay-300 animate-once State variant + direction grammar
hover:animate-wiggle · animate-fade animate-reverse Reverse plays the keyframes backwards and fill both freezes the 0% frame — the box fades OUT
and stays invisible until replayed. Direction is just another grammar class.
Composition grammar — animate-add stacks same-property animations
animate-[wiggle-more_1s_ease-in-out_infinite,wiggle_1s_ease-in-out_infinite] animate-add Both keyframes animate rotation; animate-add sums them (±3° on top of ±12°) instead of the
default replace. Composition is a property of the grammar, not of any keyframe set.
Reduced motion — the per-element pattern
motion-reduce:animate-none animate-jump-in Belt-and-suspenders for one-off entrances. The app-wide floor is the ONE prefers-reduced-motion block in src/app.css — the whole tailwindcss-animated mitigation (the plugin ships none of
its own). The page's emulation toggle gates the JS engines; the OS query is what CSS answers.
Custom keyframes path — @theme + @keyframes, then exit-color → remove
app.css registers --animate-exit-color (fade + shrink + scale-down, 1.2s) inside @theme with its @keyframes exit-color — the grammar then works over it like any
built-in. Remove a chip: it plays animate-exit-color, and the animationend handler releases it from Svelte state. Add a chip to see the plugin's animate-jump-in entrance.
@theme {
--animate-exit-color: exit-color 1.2s ease both;
@keyframes exit-color {
0% { opacity: 1; transform: scale(1); }
60% { opacity: 1; transform: scale(0.96); }
100% { opacity: 0; transform: scale(0.9); }
}
} 8 · The verdict the deliverable
The Captain's ruling (five tools adopted, each for its strength: anime.js orchestration · tailwindcss-animated grammar · animate.css vocabulary · Svelte native baseline · auto-animate convenience) is the fleet posture section at the top of this page — cite it (rows + anchors) when pointing agents at animation decisions.
| Job | Winner | Why (measured here) |
|---|---|---|
| Plain list reorder, zero setup | Svelte flip | One directive, 0 bytes, framework-maintained. The fleet survey found 798 transition: uses and 0 animate: — the native option is unmined gold. |
| Watch-the-DOM convenience (prototypes, admin lists) | auto-animate | Still the lowest-friction FLIP: attach once, forget. But no events, no stagger, baked options — the deeper you dig, the more you outgrow it. |
| Enter/exit control, stagger waves, springs, completion events | anime.js | createLayout FLIP + enterFrom/leaveTo, stagger from 'center', first-class springs, .then()/onComplete. Costs ~40kB full (tree-shaken: see README) and owns no reduced-motion — you gate it. |
| Cheap attention flourishes (pulse/shake), CSS-only stacks | animate.css | 5.3kB gzip CSS, 0 JS, built-in reduced-motion. License flag (Hippocratic-2.1) + frozen since 2020 — fine for the playground, conscious decision for the fleet. |
Capability matrix (scout t_80d86eb2 + this page)
| Capability | auto-animate 0.10.0 | anime.js 4.5.0 | animate.css 4.1.1 | Svelte native |
|---|---|---|---|---|
| List-reorder FLIP | ✔ built-in | ✔ createLayout (v4.3+) | — | ✔ animate:flip |
| Enter/exit | auto (crude) | ✔ enterFrom / leaveTo * | classes only | ✔ transition: in:/out: |
| Completion events | ✖ none | ✔ .then() + onComplete | animationend (infinite skips) | outro callbacks / events |
| Springs | ✖ | ✔ createSpring | — | bezier approx. only |
| Stagger | ✖ (#44 open) | ✔ stagger (grid/from/jitter) | ✖ (no per-index classes) | manual delay hacks |
| JS-object targets | ✖ | ✔ | n/a (CSS) | ✖ |
| Reduced motion | mount-time check | ✖ none — gate it | ✔ built-in media query | ✖ own gating |
| Bundle | 3.2kB gzip | ~40kB gzip full / less shaken | 5.3kB gzip CSS, 0 JS | 0kB |
| Health / license | active · MIT | active · MIT (v5 beta) | frozen 2020 · Hippocratic-2.1 ⚠️ | core framework |
* leaveTo caveat — the library never re-inserts detached nodes; under Svelte-owned keyed removal the row must stay mounted one beat (hidden), then release. Encoded in this page's spike section + e2e.
How to choose (decision tree — posture v2)
- JS orchestration — reorders with control, springs, stagger, completion events? anime.js — pin ^4.5.0, own the reduced-motion gate.
- CSS utility tweak (duration/delay/iteration/timing/fill/direction/composition) on ANY keyframe? tailwindcss-animated grammar — custom
@themekeyframes included. - A NAMED effect (hinge, lightSpeed, bounceIn family…)? animate.css catalog — use directly or port the keyframes (mind the license, adopted with the flag standing).
- Keyed-list reorder only? Svelte animate:flip — free, and the fleet already runs 798
transition:s. - Prototype/admin convenience, no events needed? auto-animate.
License & reduced-motion flags (posture v2)
- animejs 4.5.0: MIT, 0 deps, TS types bundled. Pin ^4.5.0 — v5.0.0-beta.2 is npm-tagged; a future `latest` flip must not surprise the fleet. @types/animejs is v3-era: do NOT install.
- animate.css 4.1.1: Hippocratic-2.1 per repo package.json + README (the npm "MIT" field is stale metadata). Frozen since 2020. POSTURE v2 RULING: consciously ADOPTED as the fleet keyframe catalog (direct use or as port sources) — the flag stands, and the equivalent keyframes remain trivially writable if a fleet app needs to bail.
- tailwindcss-animated 2.1.0: MIT, Tailwind v4 plugin (CSS utilities only, no JS runtime). Ships NO reduced-motion handling — fleet adoption includes the one-block prefers-reduced-motion mitigation in the shared app.css (this playground ships it in src/app.css). animate.css remains the only auto-RM member.