navius

Height Animation

An overflow-clipped container that WAAPI-tweens its height so a collapse, an expand, or a content-size change glides instead of jumping. The physics are solved and baked to a linear() easing in C#; JS only executes.

What it does

The container tweens its height between 0 and the natural size of this content, so a collapse or expand glides instead of jumping.

Leave Expanded null and it simply tracks the content size forever, which is handy for panels whose contents grow.

The component

NaviusHeightAnimation renders one overflow-clipped <div> (a single element, per ADR-0003, so there is no asChild) around an inner measured wrapper, and tweens the outer height to match. Splat your layout classes onto it; the outer element carries data-navius-height-animation and the inner carries data-navius-height-animation-content.

An auto-height container. Expanded distinguishes the two modes; Duration/Easing time the tween, or pass a Spring to bake it onto a linear() curve.

Prop Type Default
Expanded bool? null (track)
Duration double? 300
Easing string? "ease"
Spring Spring? -
ReduceMotion string "user"
ChildContent RenderFragment? -
Attributes IDictionary<string, object>? (splat) -

Two modes

One nullable flag distinguishes them. Expanded = null (the default) always tracks the content's natural size and never collapses, which suits a panel whose contents grow. Expanded = true / false is a collapse/expand between 0 and the natural height, and content-tracking still applies while expanded.

Collapsed content stays mounted and in the tab order (visually hidden via overflow:hidden + height:0); this does not manage inert / hidden / tabindex. For an accessibility-gated collapse use Collapsible (and drop this on its panel for the height glide).

Timing the tween

With no timing arguments the tween uses the calm defaults: 300ms, ease. Pass a Spring and it bakes to a linear() easing plus its settle duration; Duration and Easing override either dimension independently. Under prefers-reduced-motion (or ReduceMotion="always") the tween is skipped and the end state applies instantly; ReduceMotion="never" opts one container out.

Imperative runtime

The component is a thin wrapper over the interop. Bind it yourself when you need the handle: CreateHeightAnimationAsync attaches a ResizeObserver on the content, SetExpandedAsync tweens between collapsed (0) and open (natural), RemeasureAsync re-checks the content size after a change the observer could miss, and disposing disconnects the observer and cancels any in-flight tween.

Bridge to navius-motion.js. CreateHeightAnimationAsync returns a HeightAnimationMotion; build its options from a spring (or raw timing) with MotionPrograms.HeightAnimation.

Prop Type Default
CreateHeightAnimationAsync(ElementReference element, ElementReference content, HeightAnimationOptions options) Task<HeightAnimationMotion> -
MotionPrograms.HeightAnimation(Spring? spring = null, double? durationMs = null, string? easing = null, string reduceMotion = "user", bool? expanded = null) HeightAnimationOptions -

Serialized to the executor. Easing accepts a baked linear() spring string; Expanded is the initial mode.

Prop Type Default
DurationMs double 300
Easing string "ease"
ReduceMotion string "user"
Expanded bool? null

The live binding. SetExpandedAsync tweens between collapsed and open; RemeasureAsync re-tracks the content; dispose to disconnect.

Prop Type Default
SetExpandedAsync(bool? expanded) Task -
RemeasureAsync() Task -
DisposeAsync() ValueTask -