Skip to content

Motion that keeps its momentum

Spring animations you can interrupt at any moment, because every retarget and reversal carries the velocity the element already had.

Click the card, then press Esc mid-flight to reverse it.

Three packages, one spring

The same analytic spring runs in the browser, in React and in React Native. Pick the layer you work at.

Web

@damped/core

Animate elements, reorder with FLIP, morph one element into another and run springs on the compositor.

Reference for @damped/core
bun add @damped/core
npm install @damped/core
import { animate } from "@damped/core";
const card = document.getElementById("card")!;
animate(card, { x: 240 }, { bounce: 0.15 });

React 19

@damped/react

Hooks and a Presence component. Values reach the DOM without re-rendering on every frame.

Reference for @damped/react
bun add @damped/react @damped/core
npm install @damped/react @damped/core
import { useSpring } from "@damped/react";
export function Card({ open }: { open: boolean }) {
const ref = useSpring<HTMLDivElement>({ scale: open ? 1.05 : 1 });
return <div ref={ref}>Card</div>;
}

React Native

@damped/native

withDamped, a withSpring replacement for Reanimated that keeps velocity on reversal.

Reference for @damped/native
bun add @damped/native
npm install @damped/native
import { withDamped } from "@damped/native";
import { useSharedValue } from "react-native-reanimated";
export function useToggle() {
const x = useSharedValue(0);
return () => {
x.value = withDamped(x.value ? 0 : 200, { bounce: 0.15 });
};
}

Why damped

Interruptions keep their momentum

Retarget a running animation, or reverse it halfway, and the new spring starts from where the element is, moving at the speed it had. Nothing snaps back to a standstill.

Interruption and reversal

Exact state at any time

A spring is a closed-form function of time, not a per-frame integration. The same position and velocity come out at 60 Hz, at 144 Hz and on an irregular frame cadence, to within 1e-12.

Springs, explained

Smooth while the main thread is busy

The optional compositor driver hands the browser a sampled keyframe animation. In Chromium, a 250 ms busy loop left the compositor box moving while the same spring on the main thread stood still.

The compositor driver

Reduced motion is respected

Animations follow prefers-reduced-motion by default: movement jumps to its destination while fades and crossfades stay, so the interface still reads as changing.

Reduced motion and accessibility
3.95 KB
gzipped, for an app that only calls animate
6.99 KB
gzipped, for the whole of @damped/core
0
React renders per animation frame

Sizes are minified and gzipped, each entry point bundled the way an application would bundle it. The compositor driver adds about 0.74 KB and is left out unless you import it.

Keep exploring