Recipes
Short patterns for common UI, each one taken from the demo that ships with the package. Toasts, modals, tabs and screen swaps are on the transitions page, and lists on the stagger page.
Hover, press and focus Pro
playButton.OnHover().ScaleTo(1.08f, 0.12f, Ease.OutBack);
playButton.OnPress().ScaleTo(0.94f, 0.08f);
playButton.OnFocus().ScaleTo(1.08f, 0.15f, Ease.OutBack); // keyboard and gamepad get the same feedback
Declare these once when you set up the screen, not inside event handlers. Pair OnHover
with OnFocus, or the feedback only works with a mouse.
Accordion
void ToggleAccordion()
{
open = !open;
body.Animate().Height(open ? 168f : 0f, 0.3f).Ease(Ease.OutCubic);
}
Give the body overflow: hidden in USS so its contents are clipped while it closes.
Height re-runs layout every frame, which is what an accordion needs; for many elements at
once, prefer MoveX, ScaleTo and Rotate, which don't.
Named states Pro
card.Variants()
.State("normal", s => s.ScaleTo(1f).RotateTo(0f).FadeTo(1f).In(0.35f).WithSpring(SpringPreset.Snappy))
.State("featured", s => s.ScaleTo(1.2f).RotateTo(-5f).FadeTo(1f).In(0.35f).WithSpring(SpringPreset.Bouncy))
.State("dim", s => s.ScaleTo(0.9f).RotateTo(0f).FadeTo(0.4f).In(0.3f).With(Ease.OutCubic));
card.To("featured"); // later, from anywhere
Declare each state once and move between them by name. Selection, difficulty pickers and inventory slots fit this well: three states cost three declarations, not a transition for every pair.
A sequence with async/await
async void PlayIntro()
{
bool finished = await title.Animate().Fade(1f, 0.4f).From(0f).Ease(Ease.OutCubic);
if (!finished) return; // cancelled: the screen closed, so stop here
await subtitle.Animate().MoveY(0f, 0.35f).From(20f).Ease(Ease.OutCubic);
await startButton.Animate().ScaleTo(1f, 0.3f).From(0.8f).Ease(Ease.OutBack);
}
Always check the result. An intro that continues after its screen has closed is a common source of errors. Await each animation once; keep the result if you need it again.
Without async, Then() does the same and allocates nothing:
card.Animate()
.MoveY(-40f, 0.3f).Ease(Ease.OutCubic)
.Then().MoveY(0f, 0.5f).Delay(0.6f).Ease(Ease.OutBounce); // rise, hold 0.6 s, fall
Loops
var pulse = badge.Animate().ScaleTo(1.15f, 0.5f).Ease(Ease.InOutSine).Loop(LoopMode.PingPong, 0); // 0 = forever
spinner.Animate().Rotate(360f, 1f).Loop(LoopMode.Restart, 0);
Keep the handle of a loop that runs forever. Without it, the only way to stop it is
StopAnimations() on the element.
Bump something that is already moving
ship.Animate().MoveX(400f, 2f); // a long move
ship.Animate().NudgeY(-12f, 0.15f); // a hit: adds a bump without stopping the move
MoveX on an element that is already moving replaces that movement. Nudge,
NudgeX and NudgeY add on top of it instead, which is what screen shake and hit
reactions need.
Count a number up
int from = shown, to = coins;
scoreLabel.Animate().Value(0f, 1f, 0.6f, t => scoreLabel.text = Mathf.RoundToInt(Mathf.Lerp(from, to, t)).ToString())
.Ease(Ease.OutCubic);
Respect reduced motion
MotionSettings.Mode = MotionMode.Reduced; // Full, Reduced, Crossfade or None
Set it once from your accessibility settings. Every animation started afterwards follows it:
Reduced shortens and calms motion, Crossfade replaces movement with fades, and
None jumps to the end values. Callbacks and await still complete, so game logic
waiting on an animation keeps working.
For motion that carries meaning, such as a loading spinner, opt out:
spinner.Animate().Rotate(360f, 1f).Loop(LoopMode.Restart, 0).IgnoreReducedMotion();
Common mistakes
| What you see | Why | Fix |
|---|---|---|
| The fade does nothing | The element is already at the target value | Add From(0f) |
| It animates but stays invisible | A previous Hide() left it at display: none | Use Show(), not AnimateIn() |
layout.x or layout.width is 0 | Read before the first layout pass | Read it in a GeometryChangedEvent callback |
OnComplete never runs | The animation was cancelled or replaced, not finished | Use OnSettled, which runs either way |
| A bump stops the movement | MoveX replaces the running move | Use NudgeX |
| The animation stops when a screen closes | The element left the panel | Intended: Cascade cancels it so nothing writes to a closed screen |
| A staggered list feels slow | The step is too large | 0.04–0.08 s between items |