navius

Auto-Animate

One attribute on a list container animates every add, remove and reorder of its children. A single MutationObserver FLIPs survivors, new items scale and fade in, removed items pin and fade out, and the whole FLIP can ride a C#-baked spring.

Item 1
Item 2
Item 3
Item 4

The component

NaviusAutoAnimate renders one <div> (a single element, per ADR-0003, so there is no asChild) and turns it into the FLIP container. Splat your layout classes; the direct element children are the animated set, so key them and let the list drive itself. The FLIP measures real DOM moves, so keep a stable @key on each child for reorders to animate.

A FLIP-on-mutation list container. Enabled is two-way bindable; when false, mutations apply instantly. Duration/Easing time the remain, or pass a Spring to animate the FLIP on a baked linear() curve.

Prop Type Default
Enabled bool true
EnabledChanged EventCallback<bool> -
Duration double? -
Easing string? -
Spring Spring? -
ReduceMotion string "user"
ChildContent RenderFragment? -
Attributes IDictionary<string, object>? (splat) -

Timing the FLIP

With no timing arguments the FLIP uses the @formkit/auto-animate defaults: 250ms, ease-in-out. Pass a Spring and it bakes to a linear() easing plus its settle duration (springs on the FLIP is the differentiator over the reference); Duration and Easing override either dimension independently. Adds run at 1.5x the remain duration and removes at 1x, both eased the same way.

Under prefers-reduced-motion (or ReduceMotion="always") the transform animation is skipped so mutations land instantly, while adds and removes keep their opacity fade. Set ReduceMotion="never" to opt one container out.

Scroll-owner guardrail

If the container itself scrolls, auto-animate owns its scrollTop: it counter-scrolls on removals near the bottom so surviving rows do not jump. That makes it the sole writer of the scroll position.

Do not wrap a container whose scroll position is already driven by another engine, for example a chat viewport that sticks to the bottom (NaviusMessageScroller). Two writers of scrollTop will fight. Auto-animate a non-scrolling inner wrapper instead, and let the other engine own the scroll container.

Imperative runtime

The component is a thin wrapper over the interop. Bind it yourself when you need to toggle the behaviour on an existing element or hold the handle: CreateAutoAnimateAsync attaches the observer, and the handle enables or disables the FLIP without tearing it down (while disabled, mutations apply instantly). Dispose it to disconnect the observer, stop the position pollers and detach any in-flight exit clones, so nothing is orphaned.

Bridge to navius-motion.js. CreateAutoAnimateAsync returns an AutoAnimateMotion; build its options from a spring (or raw timing) with MotionPrograms.AutoAnimate.

Prop Type Default
CreateAutoAnimateAsync(ElementReference parent, AutoAnimateOptions options) Task<AutoAnimateMotion> -
MotionPrograms.AutoAnimate(Spring? spring = null, double? durationMs = null, string? easing = null, string reduceMotion = "user") AutoAnimateOptions -

Serialized to the executor. Easing accepts a baked linear() spring string, which is how a spring animates the FLIP.

Prop Type Default
DurationMs double 250
Easing string "ease-in-out"
ReduceMotion string "user"

The live binding. Enable/Disable toggle the FLIP-on-mutation behaviour; dispose to disconnect the observer and clean up.

Prop Type Default
EnableAsync() Task -
DisableAsync() Task -
DisposeAsync() ValueTask -