Goal
Why this matters
Prerequisites
Avalonia's declarative animation stack lives in Avalonia.Animation.Animation and friends. Every control derives from Animatable, so you can plug animations into styles or run them directly in code.
| Concept | Type | Highlights |
|---|---|---|
| Timeline | Animation (Animation.cs) |
Duration, Delay, IterationCount, PlaybackDirection, FillMode, SpeedRatio |
| Track | KeyFrame (KeyFrames.cs) |
Specifies a cue (0%..100%) with one or more Setters |
| Interpolation | Animator<T> (Animators/DoubleAnimator.cs, etc.) |
Avalonia ships animators for primitives, transforms, brushes, shadows |
| Easing | Easing (Animation/Easings/*) |
Over 30 easing curves, plus SplineEasing for custom cubic Bezier |
| Clock | IClock / Clock (Clock.cs) |
Drives animations, default is the global clock |
A minimal style animation:
<Window xmlns="https://github.com/avaloniaui">
<Window.Styles>
<Style Selector="Rectangle.alert">
<Setter Property="Fill" Value="Red"/>
<Style.Animations>
<Animation Duration="0:0:0.6"
IterationCount="INFINITE"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="Opacity" Value="0.4"/>
<Setter Property="RenderTransform.ScaleX" Value="1"/>
<Setter Property="RenderTransform.ScaleY" Value="1"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Opacity" Value="1"/>
<Setter Property="RenderTransform.ScaleX" Value="1.05"/>
<Setter Property="RenderTransform.ScaleY" Value="1.05"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Window.Styles>
</Window>
Key points:
Animation.IterationCount="INFINITE" loops forever; avoid pairing with Animation.RunAsync (throws by design).FillMode controls which keyframe value sticks before/after the timeline. Use FillMode="Both" for a resting value.{StaticResource} from templates or code.Animation.RunAsync and Animation.Apply let you start, await, or conditionally run animations from code-behind or view models (Animation.cs, RunAsync).
public class ToastController
{
private readonly Animation _slideIn;
private readonly Animation _slideOut;
private readonly Border _host;
public ToastController(Border host, Animation slideIn, Animation slideOut)
{
_host = host;
_slideIn = slideIn;
_slideOut = slideOut;
}
public async Task ShowAsync(CancellationToken token)
{
await _slideIn.RunAsync(_host, token); // awaits completion
await Task.Delay(TimeSpan.FromSeconds(3), token);
await _slideOut.RunAsync(_host, token); // reuse the same host, different cues
}
}
Behind the scenes RunAsync applies the animation with an IClock (defaults to Clock.GlobalClock) and completes when the last animator reports completion. Create the _slideOut animation by cloning _slideIn, switching its cues, or temporarily setting PlaybackDirection = PlaybackDirection.Reverse before calling RunAsync.
Reactive triggers map easily to animations by using Apply(control, clock, IObservable<bool> match, Action onComplete):
var animation = (Animation)Resources["HighlightAnimation"];
var match = viewModel.WhenAnyValue(vm => vm.IsDirty);
var subscription = animation.Apply(border, null, match, null);
_disposables.Add(subscription);
true pulses start it, false cancels).Clock to coordinate multiple animations (e.g., new Clock(globalClock) with PlayState.Pause to scrub).For property tweaks (hover states, theme switches) Animatable.Transitions (Animatable.cs) is lighter weight than keyframes. A Transition<T> blends from the old value to a new one automatically.
<Button Classes="primary">
<Button.Transitions>
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.150"/>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.200"/>
</Transitions>
</Button.Transitions>
</Button>
Rules of thumb:
Transitions.cs). Use styled properties or wrappers.Button.Transitions) or in a style (<Setter Property="Transitions">).<Style Selector="Button:pointerover">
<Setter Property="Opacity" Value="1"/>
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="1.02" ScaleY="1.02"/>
</Setter.Value>
</Setter>
</Style>
When the property switches, the matching Transition<T> eases between the two values. Avalonia ships transitions for numeric types, brushes, thickness, transforms, box shadows, and more (Animation/Transitions/*.cs).
AnimatorDrivenTransition lets you reuse keyframe logic as an implicit transition. Add an Animation to Transition by setting Property and plugging a custom Animator<T> if you need non-linear interpolation or multi-stop blends.
Navigation surfaces (TransitioningContentControl, Frame, NavigationView) rely on IPageTransition (PageSlide.cs, CrossFade.cs).
<TransitioningContentControl Content="{Binding CurrentPage}">
<TransitioningContentControl.PageTransition>
<CompositePageTransition>
<CompositePageTransition.PageTransitions>
<PageSlide Duration="0:0:0.25" Orientation="Horizontal" Offset="32"/>
<CrossFade Duration="0:0:0.20"/>
</CompositePageTransition.PageTransitions>
</CompositePageTransition>
</TransitioningContentControl.PageTransition>
</TransitioningContentControl>
PageSlide shifts content in/out; set Orientation and Offset to control direction.CrossFade fades the outgoing and incoming visuals.CompositePageTransition to layer multiple effects.TransitioningContentControl.TransitionCompleted to dispose view models or preload the next page.For navigation stacks, pair page transitions with parameterized view-model lifetimes so you can cancel transitions on route changes (TransitioningContentControl.cs).
Because each animation pipes through IObservable<bool> internally, you can stitch motion into reactive pipelines:
match observables allow gating by business rules (focus state, validation errors, elapsed time).Animation.Apply(control, clock, observable, onComplete) to bind to WhenAnyValue, Observable.Interval, or custom subjects.IDisposable unsubscribes transitions when your view deactivates (critical for Animatable.DisableTransitions).Example: flash a text box when validation fails, but only once every second.
var throttle = validationFailures
.Select(_ => true)
.Throttle(TimeSpan.FromSeconds(1))
.StartWith(false);
animation.Apply(textBox, null, throttle, null);
Avalonia's compositor (Compositor.cs) mirrors the Windows Composition model: a scene graph of CompositionVisual objects runs on a dedicated thread and talks directly to GPU backends. Advantages:
Getting the compositor:
var elementVisual = ElementComposition.GetElementVisual(myControl);
var compositor = elementVisual?.Compositor;
You can inject custom visuals under an existing control:
var compositor = ElementComposition.GetElementVisual(host)!.Compositor;
var root = ElementComposition.GetElementVisual(host) as CompositionContainerVisual;
var sprite = compositor.CreateSolidColorVisual();
sprite.Color = Colors.DeepSkyBlue;
sprite.Size = new Vector2((float)host.Bounds.Width, 4);
sprite.Offset = new Vector3(0, (float)host.Bounds.Height - 4, 0);
root!.Children.Add(sprite);
When mixing visuals, ensure they come from the same Compositor instance (ElementCompositionPreview.cs).
CompositionTarget (CompositionTarget.cs) owns the visual tree that the compositor renders. It handles hit testing, coordinate transforms, and redraw scheduling. Most apps use the compositor implicitly via the built-in renderer, but custom hosts (e.g., embedding Avalonia) can create their own target (Compositor.CreateCompositionTarget).
The compositor supports more than simple solids:
CompositionColorBrush and CompositionGradientBrush mirror familiar WPF/UWP concepts and can be animated directly on the render thread.CompositionEffectBrush applies blend modes and image effects defined in Composition.Effects. Use it to build blur/glow pipelines without blocking the UI thread.CompositionExperimentalAcrylicVisual ships a ready-made fluent-style acrylic material. Combine it with backdrop animations for frosted surfaces.CompositionDrawListVisual lets you record drawing commands once and replay them efficiently; great for particle systems or dashboards.Use Compositor.TryCreateBlurEffect() (platform-provided helpers) to probe support before enabling expensive effects. Not every backend exposes every effect type; guard features behind capability checks.
Composition runs on different engines per platform:
DwmIsCompositionEnabled).TransparencyLevel and composition availability via X11Globals.IsCompositionEnabled.When features are missing, prefer classic transitions so the experience remains functional.
Composition animations live in Avalonia.Rendering.Composition.Animations:
ExpressionAnimation lets you drive properties with formulas (e.g., parallax, inverse transforms).KeyFrameAnimation offers high-frequency GPU keyframes.ImplicitAnimationCollection attaches animations to property names and fires when the property changes (CompositionObject.ImplicitAnimations).Example: create a parallax highlight that lags slightly behind its host.
var compositor = ElementComposition.GetElementVisual(header)!.Compositor;
var hostVisual = ElementComposition.GetElementVisual(header)!;
var glow = compositor.CreateSolidColorVisual();
glow.Color = Colors.Gold;
glow.Size = new Vector2((float)header.Bounds.Width, 4);
ElementComposition.SetElementChildVisual(header, glow);
var parallax = compositor.CreateExpressionAnimation("Vector3(host.Offset.X * 0.05, host.Offset.Y * 0.05, 0)");
parallax.SetReferenceParameter("host", hostVisual);
parallax.Target = nameof(CompositionVisual.Offset);
glow.StartAnimation(nameof(CompositionVisual.Offset), parallax);
For property-driven motion, use implicit animations: create an ImplicitAnimationCollection, add an animation keyed by the composition property name (for example nameof(CompositionVisual.Opacity)), then assign the collection to visual.ImplicitAnimations. Each time that property changes, the compositor automatically plays the animation using this.FinalValue inside the expression to reference the target value (ImplicitAnimationCollection.cs).
StartAnimation pushes the animation to the render thread. Use CompositionAnimationGroup to start multiple animations atomically, and Compositor.RequestCommitAsync() to flush batched changes before measuring results.
RenderTransform, Opacity) over layout-affecting properties (Width, Height). Layout invalidation happens on the UI thread and can stutter.Animatable.DisableTransitions). Re-enable after the initial bind.Compositor.RequestCommitAsync() coalesce writes instead of spamming per-frame updates.RendererDiagnostics overlays (Chapter 24) to spot dropped frames and long render passes. Composition visuals show up as separate layers, so you can verify they batch correctly.BrushTransition.cs). Verify gradients or image brushes blend the way you expect.NotificationBanner, then start it from a view model with RunAsync. Add cancellation so repeated notifications restart smoothly.Transitions block for cards in a dashboard: fade elevation shadows, scale the card slightly, and update TranslateTransform.Y. Drive the transitions purely from pseudo-classes.TransitioningContentControl. Combine PageSlide with CrossFade, listen for TransitionCompleted, and cancel transitions when the navigation stack pops quickly.Document timing curves, easing choices, and any performance issues so the team can iterate on the experience.
Target matches the composition property name (case-sensitive).RunAsync throw—drive infinite loops with Apply or manual scheduler instead.RenderTransform and its sub-properties simultaneously causes conflicts. Use a single TransformOperationsTransition to animate complex transforms.Size and Offset whenever the host control's bounds change, then call Compositor.RequestCommitAsync() to flush.Children.Remove) before adding a new one, or cache the sprite in the control instance.external/Avalonia/src/Avalonia.Base/Animation/Animation.csexternal/Avalonia/src/Avalonia.Base/Animation/Transitions.csexternal/Avalonia/src/Avalonia.Base/Animation/PageSlide.cs, external/Avalonia/src/Avalonia.Base/Animation/CrossFade.csexternal/Avalonia/src/Avalonia.Base/Rendering/Composition/Compositor.cs, external/Avalonia/src/Avalonia.Base/Rendering/Composition/CompositionTarget.csexternal/Avalonia/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs, external/Avalonia/src/Avalonia.Base/Rendering/Composition/CompositionExperimentalAcrylicVisual.cs, external/Avalonia/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionAnimation.csexternal/Avalonia/src/Avalonia.Base/Rendering/Composition/CompositionObject.csDoubleTransition over a keyframe animation, and why does that matter for layout cost?IterationCount, FillMode, and PlaybackDirection interact to determine an animation's resting value?What's next