# animationKey — play an animation again when a value changes; a line that draws itself

> `animationKey: value` on any element makes its animation play each time the value CHANGES — and not when the element first appears, so a page load nudges nothing and the count that later moves is the one the eye goes to. `StrokeDasharray`/`StrokeDashoffset` (and the kit's `KitDraw`) make a line draw itself: a check that writes itself when something lands.

<!-- id: ui-animation-key · area: ui · stability: preview · html: https://osysharp.com/reference/ui/animation-key/ -->

## Summary        {#summary}
An `animation` plays when its element appears. A COUNT wants the opposite: it should move when the number moves, and
never on a page load, when nothing changed. `animationKey: value` says exactly that — the element's animation plays
again each time `value` changes, and is held still when the element first appears (server paint included). The kit's
nav badges, the shell's notification count and its pin all nudge this way.

A line that DRAWS itself — the tick that writes itself when a save lands — is two style props and one kit animation:
`StrokeDasharray`/`StrokeDashoffset` hide a stroke behind a dash and slide the dash along it, and `KitDraw` does that
for any line up to 24 units of the icon grid. `Done("Saved")` is the tick and its word, ready-made.

## Signature      {#signature}
```osy syntax
Badge(unread.ToString(), animation: KitNudge, animationKey: unread);           // nudges when `unread` changes
Box(animation: KitDraw) { Icon(Icons.Check); }                                  // the tick draws as it appears
Box(animation: KitDraw, animationKey: savedAt) { Icon(Icons.Check); }           // …and again on every save
Done("Saved")                                                                   // a drawn tick and its word
```

## Description    {#description}

### Plays on the change, not on the first appearance   {#change}
`animationKey:` takes any value — a number, a string, a bool, an enum — and is accepted on every atom, like
`animation:` itself. When the value changes between two renders, the element's animation restarts from its first frame;
a re-render that leaves the value alone does nothing. On the element's FIRST appearance the animation is held off —
nothing changed then, and a screen of twenty counts that all nudge on load hides the one that later moves. An element
that should also ENTER is wrapped: the entrance on the outer box, the key on the inner one.

On a component call (`Badge`, `Card`) both go on the call, and land on the component's root element
([Placing and naming a component — m:, w:, grow:, alignSelf:, animation:, label: on a component call](https://osysharp.com/reference/ui/component-root-style/)): `Badge(n.ToString(), animation: KitNudge, animationKey: n)`.

### A line that draws itself   {#draw}
`StrokeDasharray` sets the dash pattern of every line drawn inside the element and `StrokeDashoffset` how far along the
line the pattern starts; both INHERIT, so on the box around an `Icon` they reach the icon's own strokes. A dash as long
as the line, offset by that length, hides it; animating the offset to `0` draws it. `KitDraw` is exactly that
(`24` → `0` over 320ms), sized for the 24-unit icon grid — every tick and circle-tick in the set draws whole.

`Done(label = "Saved", tone = Tone.Success)` is a tick that draws as it appears beside its word, announced politely
(`role: Status`). The kit's `Checkbox` draws its tick when it is ticked — keyed to its value, so a form of ticked boxes
does not draw on load.

### Reduced motion   {#reduced-motion}
Nothing to write. A nudge is a scale, which reduced motion drops; a stroke's travel (`StrokeDashoffset`) is dropped
too, so the tick is simply there. The key still changes; only the movement is gone.

## Examples       {#examples}
```osy title="a count that nudges when it moves, and a tick that draws when something is saved" test app=ui-animation-key
[Route("/")]
[AllowAnonymous]
component Queue() {
  int waiting = 3;
  int saves = 0;
  action Arrive() { waiting = waiting + 1; }
  action Save() { saves = saves + 1; }
  render {
    Stack(gap: 3, p: 6) {
      Row(gap: 2, align: Align.Center) {
        Text("Waiting on you");
        // Key it to the count: it nudges when a ticket arrives, never when the page loads.
        Badge(waiting.ToString(), tone: Tone.Warning, animation: KitNudge, animationKey: waiting);
      }
      Button("Simulate a ticket", onPress: Arrive);
      Row(gap: 2, align: Align.Center) {
        Button("Save", onPress: Save, tone: Tone.Primary);
        // A drawn tick, again on every save.
        if (saves > 0) {
          Box(animation: KitDraw, animationKey: saves, color: Colors.Success) { Icon(Icons.Check, size: 18); }
        }
      }
      if (saves > 0) { Done(); }
    }
  }
}
```

### Testing them   {#testing}
Text assertions see the words, not the motion: `Assert.Visible("4")` after the press, `Assert.Visible("Saved")`. Whether
the motion plays is a real-browser question — `osy test --pixels` photographs the page, and the platform's own visual
gate checks the replay mechanism.

## See also   {#see-also}
- [animation — keyframe motion, on entry, on exit, in a loop, or as it scrolls into view](https://osysharp.com/reference/ui/animation/) — declaring an animation, and the kit's motion vocabulary
- [style props](https://osysharp.com/reference/ui/styling/) — every style prop, the stroke props among them
- [Pinned and recent pages, the app switcher, and presence in a shell](https://osysharp.com/reference/ui/shell-shortcuts/) — the shell's counts and pin, which nudge this way
