Triggers
Trigger is a [Flags] enum, so an icon can loop and respond to a tap.
[Flags]public enum MotionTrigger{ Manual = 0, Loop = 1, Hover = 2, Press = 4, Appear = 8}The default is Hover | Press — hover for desktop, press for touch, so an icon does something sensible everywhere without being told.
Trigger is bindable and can change at any time. On MAUI, switching to Loop or Appear after the icon has loaded starts it, and switching away from Loop stops it — the same as if the value had been there from the start.
Runs continuously. Interval inserts a resting gap between cycles, which is almost always what you want: a bell that rings without pause reads as broken.
<shiny:MotionIconView Icon="bell" Trigger="Loop" Interval="0:0:1.5" />The gap is folded into the animation itself rather than scheduled by a timer. That is what keeps the two hosts honest — a CSS animation has no way to pause between iterations, so a spec that expressed the gap externally would need a JavaScript timer on the web and a dispatcher timer on MAUI, and the two would drift apart. Squeezing the keys into the front of a longer cycle and holding the resting pose through the remainder produces the same result with nothing but animation-iteration-count: infinite.
Runs while the pointer is over the icon. When the pointer leaves, the icon finishes the cycle it is in and then stops, so a half-swung bell settles upright instead of snapping.
<MotionIcon Icon="settings" Trigger="MotionTrigger.Hover" />Hover needs a pointer, so pair it with Press for touch — which the default already does.
One play per tap or click.
<shiny:MotionIconView Icon="heart" Trigger="Press" Command="{Binding LikeCommand}" />On MAUI a Command is invoked whether or not Press is set, so the icon can act as a button while some other trigger drives its motion. On Blazor the equivalent is OnClick.
Press only hears presses that land on the icon itself. An icon sitting inside a larger tap target — a button, a list row — should be Manual, with the host playing it, or a click on the label around it will animate nothing. That is exactly what ShinyButton does with its slot icons on both hosts.
RepeatCount controls how many cycles a triggered play runs for — set it to 2 for a double-take, or 0 to keep going until something stops it.
Appear
Section titled “Appear”Plays once when the icon first becomes visible. On the web this is an IntersectionObserver; on MAUI it fires when the view loads.
<MotionIcon Icon="check" Trigger="MotionTrigger.Appear" Title="Saved" />It is deliberately one-shot. An icon that replays every time it scrolls past is a distraction; if you want that, drive IsPlaying yourself.
Manual, and busy states
Section titled “Manual, and busy states”Manual is the absence of any automatic trigger. Drive it with the methods, or — far more often — bind IsPlaying:
<shiny:MotionIconView Icon="loader" Trigger="Manual" IsPlaying="{Binding IsBusy}" /><MotionIcon Icon="loader" Trigger="MotionTrigger.Manual" IsPlaying="@busy" />While the flag is set the icon loops; when it clears, the icon returns to its resting pose. For the spinner that means settling back into a full three-quarter ring rather than snapping wider the instant it stops.
| Method | Does |
|---|---|
Play() |
Plays for RepeatCount cycles. |
Loop() |
Blazor only — plays continuously. |
PlayAsync() |
MAUI only — completes when the animation finishes. |
Stop() |
Stops now and returns to rest. |
StopAtCycleEnd() |
Lets the current cycle finish, then stops. |
Reset() |
MAUI only — back to rest without changing playback. |
StopAtCycleEnd() is what the hover trigger uses internally, and it is usually the better choice when a user-visible state ends: it costs at most one more cycle and always finishes on the resting pose.
Scrubbing (MAUI)
Section titled “Scrubbing (MAUI)”Because evaluating the animation is a pure function of progress — nothing reads the previous frame — an icon can be driven directly from a slider or a gesture:
<shiny:MotionIconView Icon="menu" Trigger="Manual" Progress="{Binding Source={x:Reference Scrubber}, Path=Value}" />
<Slider x:Name="Scrubber" Minimum="0" Maximum="1" />Dragging the slider morphs the hamburger into a cross and back. The same property reports position while the animation plays.


