> ## Documentation Index
> Fetch the complete documentation index at: https://docs.napps.io/llms.txt
> Use this file to discover all available pages before exploring further.

# React animations

Use `style.transition` for ordinary style changes caused by React state when no native gesture
lifecycle is involved. Do not use it as the pressed or selected interaction contract. Wrap
interactive content in `gestureDetector` and use shared values when feedback is gesture-driven,
interruptible, or needs to be cancelled or awaited.

## Simple transitions

Use a transition when React state changes a style value and no imperative animation control is needed.

```tsx theme={null}
export function StatusBanner({ visible }: { visible: boolean }) {
  return (
    <box
      style={{
        alpha: visible ? 1 : 0,
        transition: "alpha 180ms ease-out"
      }}
    >
      <text>Changes saved</text>
    </box>
  );
}
```

This is appropriate for a regular state-driven visibility change. For pressed or selected
feedback, use `gestureDetector` and shared values so the visual state follows the native gesture
lifecycle.

## Shared values

Use `sv` or `sharedValue` for a mutable value that updates outside normal React render flow.

```tsx theme={null}
import { useEffect } from "react";
import { animatedProps, sv } from "@napps/nodes";

export function LoadingIndicator() {
  const opacity = sv(0.4);

  useEffect(() => {
    opacity.value = 1;
  }, []);

  return <box style={animatedProps({ alpha: opacity })} />;
}
```

Always read and write `.value`. Assigning a number or color updates immediately; assigning an animation handle starts an animation.

## Timing animation

`timing` moves a value to a target over a duration using an easing curve.

```tsx theme={null}
import { useEffect } from "react";
import { animatedProps, sv, timing } from "@napps/nodes";

export function SlideIn() {
  const x = sv(-120);

  useEffect(() => {
    x.value = timing(0, { duration: 300, easing: "quadOut" });
  }, []);

  return <box style={animatedProps({ translationX: x })} />;
}
```

## Spring animation

`spring` is useful for physical movement and settling after a gesture.

```tsx theme={null}
import { useState } from "react";
import { animatedProps, spring, sv } from "@napps/nodes";

export function SpringPanel() {
  const [open, setOpen] = useState(false);
  const height = sv(0);

  function toggle() {
    setOpen((value) => !value);
    height.value = spring(open ? 0 : 240, {
      response: 0.35,
      dampingFraction: 0.8
    });
  }

  return (
    <touchable onPress={toggle}>
      <box style={animatedProps({ height })}>
        <text>{open ? "Close" : "Open"}</text>
      </box>
    </touchable>
  );
}
```

## Decay animation

`withDecay` continues movement from an initial velocity and can stop at bounds.

```tsx theme={null}
import { animatedProps, sv, withDecay } from "@napps/nodes";

export function MomentumCard() {
  const offset = sv(0);

  return (
    <gestureDetector
      gesture="pan"
      onEnd={(event) => {
        offset.value = withDecay({
          velocity: event.velocityX,
          deceleration: 0.998,
          clamp: [-240, 240]
        });
      }}
    >
      <box style={animatedProps({ translationX: offset })}>
        <text>Release to coast</text>
      </box>
    </gestureDetector>
  );
}
```

## Delay, sequence, and repeat

Use composition helpers when multiple animations form one behavior.

```tsx theme={null}
import { useEffect } from "react";
import {
  animatedProps,
  delayed,
  repeat,
  sequence,
  sv,
  timing,
  withDelay,
  withRepeat,
  withSequence
} from "@napps/nodes";

export function AttentionPulse() {
  const alpha = sv(1);

  useEffect(() => {
    alpha.value = withSequence(
      withDelay(150, timing(0.45, { duration: 120 })),
      withRepeat(timing(1, { duration: 250 }), 3, true)
    );
  }, []);

  // Factory helpers are useful when the same behavior must be awaited or reused:
  const pulse = sequence(
    delayed(150, (callback) => timing(0.45, { duration: 120 }, callback)),
    repeat((callback) => timing(1, { duration: 250 }, callback), 3, true)
  );
  void pulse;

  return <box style={animatedProps({ alpha })} />;
}
```

`withRepeat` repeats an animation directly. `repeat` creates a reusable factory. Create fresh handles for each run.

## Keyframes

Use `keyframes` for multiple stops with different values or easing per segment.

```tsx theme={null}
import { useEffect } from "react";
import { animatedProps, keyframes, sv } from "@napps/nodes";

export function ProgressSweep() {
  const progress = sv(0);

  useEffect(() => {
    progress.value = keyframes(
      [
        { offset: 0, value: 0 },
        { offset: 0.25, value: 100, easing: "quadOut" },
        { offset: 1, value: 50, easing: "linear" }
      ],
      { duration: 800 }
    );
  }, []);

  return <box style={animatedProps({ translationX: progress })} />;
}
```

## Derived values

Derived values update automatically from other reactive values and cannot be assigned directly.

```tsx theme={null}
import { useEffect } from "react";
import { animatedProps, clamp, interpolate, sv } from "@napps/nodes";

export function ScrollHeader() {
  const scrollY = sv(0);
  const headerAlpha = interpolate(scrollY, [0, 120], [1, 0]);
  const headerOffset = clamp(scrollY, 0, 64);

  useEffect(() => {
    scrollY.value = 80;
  }, []);

  return (
    <box
      style={animatedProps({
        alpha: headerAlpha,
        translationY: headerOffset
      })}
    >
      <text>Header</text>
    </box>
  );
}
```

`add` and `multiply` combine reactive values into another derived value.

## Gesture-driven animation

Use `mapEvent` for high-frequency event fields and settle the value with `spring` when the gesture ends.

```tsx theme={null}
import { animatedProps, mapEvent, spring, sv } from "@napps/nodes";

export function DraggableCard() {
  const x = sv(0);

  return (
    <gestureDetector
      gesture={{ type: "pan", minDistance: 8 }}
      onUpdate={mapEvent({ translationX: x })}
      onEnd={(event) => {
        x.value = spring(0, { velocity: event.velocityX });
      }}
    >
      <box style={animatedProps({ translationX: x })}>
        <text>Drag me</text>
      </box>
    </gestureDetector>
  );
}
```

## Awaiting and cancelling

Use promise helpers when the next action depends on completion.

```tsx theme={null}
import { useEffect } from "react";
import {
  runAnimation,
  runAnimationOrThrow,
  runParallel,
  sv,
  toSpring,
  toTiming
} from "@napps/nodes";

export function CoordinatedAnimation() {
  const x = sv(0);
  const opacity = sv(0);

  useEffect(() => {
    let cancelled = false;

    async function run() {
      await runAnimation(x, toSpring(100));
      if (cancelled) return;

      await runParallel([
        { value: x, animation: toTiming(0, { duration: 250 }) },
        { value: opacity, animation: toTiming(1, { duration: 250 }) }
      ]);

      await runAnimationOrThrow(opacity, toTiming(0, { duration: 250 }));
    }

    void run();
    return () => {
      cancelled = true;
    };
  }, []);

  return <text>Animation sequence</text>;
}
```

`runAnimation` resolves with an `AnimationResult`. `runAnimationOrThrow` rejects with `AnimationCompletionError` when the animation does not finish naturally. Use `cancelAnimation(value)` when you need to stop an active animation explicitly.

## Completion reasons

Callbacks and promise helpers report:

* `completed`: reached the target naturally.
* `cancelled`: stopped with `cancelAnimation`.
* `replaced`: another animation took over the same value.
* `overwritten`: a direct `.value` assignment replaced the animation.

## Best practices

* Prefer `style.transition` for ordinary state-driven visual changes, not pressed or selected
  feedback that depends on a native gesture lifecycle.
* Use `mapEvent` for high-frequency gesture and scroll updates.
* Create fresh animation handles; do not reuse them in multiple compositions.
* Cancel or invalidate async animation flows when the component unmounts.
* Animate only fields supported by `animatedProps`.
