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.
@* One attribute. Key the children so reorders FLIP; splat your own layout. *@
<NaviusAutoAnimate Spring="Spring.Default" class="flex flex-col gap-2">
@foreach (var item in items)
{
<div @key="item">Item @item</div>
}
</NaviusAutoAnimate>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.
@using Navius.Motion
@* Defaults (250ms, ease-in-out): *@
<NaviusAutoAnimate class="..."> ... </NaviusAutoAnimate>
@* A spring on the FLIP, and disable-able while a bulk edit runs: *@
<NaviusAutoAnimate Spring="Spring.Snappy" @bind-Enabled="_animate" class="..."> ... </NaviusAutoAnimate>NaviusAutoAnimate
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.
@inject IJSRuntime JS
var interop = new MotionJsInterop(JS);
var options = MotionPrograms.AutoAnimate(Spring.Default);
_handle = await interop.CreateAutoAnimateAsync(_listElement, options);
// Pause the FLIP for a bulk splice, then resume (re-baselines the children):
await _handle.DisableAsync();
// ... mutate the list instantly ...
await _handle.EnableAsync();
// Later: await _handle.DisposeAsync();MotionJsInterop
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 | - |
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" |
AutoAnimateMotion
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 | - |