Skip to content
Document DB 13 - MCP Server, REST API, Field Level Encryption, Transactional Outbox, & More!SHOW ME!!

Keyframe Animation

Declarative keyframe animation for .NET MAUI — the CSS @keyframes model expressed in XAML, plus a fluent C# timeline API, storyboards, a drawable scene graph, and an optional headless frame exporter.

The thing MAUI’s own Animation class cannot do is seek. Everything here is built around one design constraint: evaluating an animation is a pure function of time. Nothing reads the previous frame. That single property is what makes scrubbing, mid-flight reversal, byte-identical export, and non-flaky timing tests possible rather than merely approximable.

  • NuGet downloads for Shiny.Maui.Controls.Keyframe
  • NuGet downloads for Shiny.Maui.Controls.Keyframe.Export
Frameworks
.NET MAUI
Operating Systems
iOS
Android
Windows
macOS
Package What it is Depends on
Shiny.Controls.Keyframe.Shared The host-neutral timing engine and scene graph — Timing, Timeline, Storyboard, Track<T>, IInterpolator<T>, Player, the easing curves, KeyframeScene and its layers. Microsoft.Maui.Graphics
Shiny.Maui.Controls.Keyframe The XAML surface (Animate.Keyframes, Keyframes / Track / Key), KeyframeView, MauiClock, and the animatable-property registry. the above + Microsoft.Maui.Controls
Shiny.Maui.Controls.Keyframe.Export Headless frame export and a pure-managed GIF encoder. Optional — and the only thing in Keyframe that pulls in SkiaSharp. the shared package + Microsoft.Maui.Graphics.Skia

Install only what you need. Animating views never requires the export package.

  • CSS @keyframes semantics in XAMLDuration, Delay, Iterations, Direction, Fill, and per-key easing behave the way the web spec says they do, including Fill="Both" and Direction="Alternate".
  • Seekable, reversible playbackSeekProgress(0.35) scrubs, Rate = -1 reverses from wherever the animation currently sits. No rebuilding, no restart.
  • CSS easing function syntaxcubic-bezier(0.34, 1.56, 0.64, 1), steps(8), and spring(0.35, 14) parse from a plain string, so a curve copied out of a design tool or browser devtools pastes in verbatim. 38 named curves too.
  • Implicit keyframes — omit Value on a key and it resolves to the target’s live value when playback starts, so a re-triggered animation continues from where it is instead of snapping.
  • Oklab colour blending by default — sRGB interpolation dips through grey at the midpoint; Oklab doesn’t. ColorInterpolator.Srgb and .LinearRgb remain available.
  • Shortest-arc anglesRotation from 350° to 10° turns forward 20°, not back 340°. Spin is there for when multiple turns are the point.
  • Fluent C# timelines and storyboardsTimelineBuilder composes tracks; Storyboard sequences, overlaps and staggers whole timelines on a shared clock, and nests.
  • A drawn scene graphKeyframeScene + KeyframeView run the same timing model against layers on a canvas (the Lottie-shaped lane) rather than views in the visual tree.
  • Deterministic offscreen export — sample a scene at exact frame times, lazily, and encode to GIF with no external dependency.
  • AOT and trim safe — the property registry is hand-registered delegates, not reflection and not compiled Expression trees, both of which work in the emulator and break on device under Native AOT.
  • Weak targets — an infinite animation on a popped page goes inert and is collected, rather than pinning the visual tree.
claude plugin marketplace add shinyorg/skills
claude plugin install controls@shiny
copilot plugin marketplace add https://github.com/shinyorg/skills
copilot plugin install controls@shiny
View controls Plugin
Terminal window
dotnet add package Shiny.Maui.Controls.Keyframe
xmlns:kf="http://shiny.net/maui/keyframe"

No builder.Use…() call is needed — there is nothing to register. That one XAML namespace covers the shared timing types as well.

An attached property on any VisualElement:

<Border>
<kf:Animate.Keyframes>
<kf:Keyframes Duration="0:0:1.2" Iterations="Infinite" Direction="Alternate" Fill="Both">
<kf:Track Property="Scale">
<kf:Key Offset="0" Value="1" />
<kf:Key Offset="0.5" Value="1.15" Easing="CubicOut" />
<kf:Key Offset="1" Value="1" />
</kf:Track>
<kf:Track Property="BackgroundColor">
<kf:Key Offset="0" Value="#2563EB" />
<kf:Key Offset="1" Value="#EC4899" />
</kf:Track>
</kf:Keyframes>
</kf:Animate.Keyframes>
</Border>

The same animation in C#:

using Shiny.Controls.Keyframe;
using Shiny.Maui.Controls.Keyframe;
var timeline = TimelineBuilder
.Create(TimeSpan.FromSeconds(1.2))
.PingPong()
.RepeatForever()
.Fill(FillMode.Both)
.Animate(box, (v, x) => v.Scale = x, k => k
.From(1)
.Key(0.5, 1.15, Easings.CubicOut)
.To(1))
.Build();
var player = box.Play(timeline);

IAnimationNode.Evaluate(t) computes the state at t from the keyframes alone. It never looks at the previous frame, and it never accumulates. Write code that leans on this rather than working around it:

You want to… Do this Not this
Scrub from a Slider or gesture player.SeekProgress(x), or bind KeyframeView.Progress two-way Rebuild a timeline at the new position
Reverse mid-flight player.Rate = -1 (or a negative Speed) Build a second, reversed timeline
Export frames Sample at exact frame times — identical bytes every run Render off a real-time clock and hope
Test timing Step a ManualClock Task.Delay and a tolerance
  • Everything ticks in managed code on the UI thread. Transform and opacity tracks map 1:1 onto CAKeyframeAnimation / ObjectAnimator / ScalarKeyFrameAnimation, so native compositor offload is the design’s biggest remaining performance win — but it isn’t implemented yet.
  • Layout-affecting properties cost a measure/arrange every frame. WidthRequest, HeightRequest, Margin, and Padding all work and are flagged by AnimatableProperty.InvalidatesLayout, but prefer transform and opacity where you have the choice.
  • There is no Lottie parser. KeyframeScene is the scene graph one would need, but nothing reads a Lottie JSON file today.
  • MP4/video export is out of scope — that needs a real codec. Pipe FrameExporter.Frames() to ffmpeg’s stdin instead. APNG and WebP aren’t implemented either.