← All examples

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 record

The 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.

ToolUse it forWhyDemo
anime.js 4.5JS-driven animation: reorders with control, springs, stagger, completion eventsThe Captain test-drove it: full orchestration control with real completion events.see demo →
tailwindcss-animatedThe UTILITY GRAMMAR: duration/delay/iteration/timing/fill/direction/composition classes over ANY keyframesTailwind v4-native; composes over custom @theme keyframes too.see demo →
animate.cssThe KEYFRAME CATALOG: ~97 ready-made effects usable directly or as portsFrozen since 2020 but still the fastest vocabulary; also the only auto-RM member.see demo →
Svelte nativeFree reorder FLIP for keyed lists (animate:flip) and in:/out: transitions0 kB, framework-maintained; the fleet survey found 798 transition: uses and 0 animate:.see demo →
@formkit/auto-animateZero-config watch-the-DOM convenience for prototypes/admin listsAttach 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).

OS preference: no-preference · effective gate: full motion

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 —

@formkit/auto-animate 3.2kB

Watches the container's children — reorder detection is automatic, no per-frame code.

anime.js 4.5 · createLayout ~40kB full

Svelte mutates state → flushSync() inside layout.update() — the keyed-each reorder lands inside the FLIP snapshot window.

Svelte animate:flip 0kB

Built 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.

@formkit/auto-animate

Enter/exit are automatic — the ghost machinery you saw in the scrollbar saga.

anime.js · enterFrom / leaveTo

Leave rows: library never resurrects detached nodes — the recipe keeps them mounted one beat, hidden + leaveTo, released on onComplete.

Svelte 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.

anime.js — the wave unique
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Ripple from the center outward — stagger() through layout.update params.

@formkit/auto-animate not offered

No stagger API — items animate simultaneously (formkit #44, open). Chaining delays manually defeats the point.

Svelte native not offered

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.

anime.js — createSpring unique
🏀

The ball: keyframed squash+bounce on a spring ease. The list: the SAME spring as the layout ease — physics flow through layout.update.

@formkit/auto-animate not offered

Fixed easing strings only; no physics, no velocity carry-over.

Svelte native approx. only

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.

@formkit/auto-animate no events

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).

anime.js .then() + onComplete

Trigger cleanup →

Every animation/timeline is a Timer: .then(), onComplete, seek/cancel/restart — the full lifecycle auto-animate lacks.

Svelte transitions outro callbacks

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).

Pulse

attention seeker

Shake X

validation error

Fade In Up

one-shot entrance

Hinge

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.35
PULSE-STRONG
animate-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

jump-in · delay-300 · once
animate-jump-in animate-delay-300 animate-once

State variant + direction grammar

hover:animate-wiggle fade · reverse
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

wiggle-more + wiggle, added
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
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); }
    }
}
ship-it on-call hotfix

8 · The verdict the deliverable

DECIDED The posture on this page is the current policy — this panel is the measurement history.

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.

Verdict One job, one engine — no single winner. Per-pattern winners measured on this page.
JobWinnerWhy (measured here)
Plain list reorder, zero setupSvelte flipOne 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-animateStill 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 eventsanime.jscreateLayout 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 stacksanimate.css5.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)

Capabilityauto-animate 0.10.0anime.js 4.5.0animate.css 4.1.1Svelte native
List-reorder FLIP✔ built-in✔ createLayout (v4.3+)✔ animate:flip
Enter/exitauto (crude)✔ enterFrom / leaveTo *classes only✔ transition: in:/out:
Completion events✖ none✔ .then() + onCompleteanimationend (infinite skips)outro callbacks / events
Springs✔ createSpringbezier approx. only
Stagger✖ (#44 open)✔ stagger (grid/from/jitter)✖ (no per-index classes)manual delay hacks
JS-object targetsn/a (CSS)
Reduced motionmount-time check✖ none — gate it✔ built-in media query✖ own gating
Bundle3.2kB gzip~40kB gzip full / less shaken5.3kB gzip CSS, 0 JS0kB
Health / licenseactive · MITactive · 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 @theme keyframes 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.