# react-spring: a spring-physics animation library for React

> react-spring animates React components by simulating spring physics instead of fixed durations and easing curves. This review covers how the hook model works, how to install @react-spring/web, where the physics-first approach costs you control, and how it differs from Framer Motion.

**pmndrs/react-spring** — ✌️ A spring physics based React animation library

- Repository: https://github.com/pmndrs/react-spring
- Website: http://www.react-spring.dev/
- Stars: 29,160 · Forks: 1,218
- Language: TypeScript
- License: MIT
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/pmndrs-react-spring

## The problem react-spring solves: motion that has to react mid-flight

A CSS transition or a keyframe animation commits to a duration and an easing curve up front. That works for a button hover. It breaks down when the target value changes while the animation is still running, which is exactly what happens with drag gestures, scroll-linked reveals, and toggles a user can flip faster than the transition finishes. The README frames the library as "a spring-physics first animation library," and the operative word is physics. Instead of interpolating between two points over a fixed time, the library models the motion as a spring with a target, and when the target moves, the spring is already in motion and simply redirects.

The audience follows from that. This is for React developers building interactive surfaces: cards that follow a pointer and settle back, panels that open and close at whatever rhythm the user provides, three-dimensional scenes where an object's position is animated rather than set. If your animation is a one-shot entrance that plays once and never changes target, spring physics buys you very little over a CSS keyframe. The README's own first example is a fade and a small vertical offset driven by an isVisible boolean, which is the simplest possible case and not really the one the library is designed around.

The README also states the library is cross-platform, supporting react-dom and react-three-fiber, and that it is versatile enough to be used declaratively or imperatively. That second claim matters more than it looks. The declarative path is the hook API; the imperative path exists for cases where you want to start and stop animations outside the render cycle, and the README does not document that path in the excerpt available here.

## How the spring model works in practice

The core of the API is a hook that takes a target configuration and returns a style object you attach to an animated component. The README's jump-start example shows the shape: a useSpring call receiving an object with opacity and y keys, whose values are the current targets, and an animated.div receiving the returned styles as its style prop.

The mechanism, as the README presents it, is that the hook does not receive a from and a to as separate props in the newer form. It receives the current target values, and the spring animates toward whatever those values are on this render. When isVisible flips, the component re-renders with new target values, and the spring retargets from wherever it currently is. That is the whole trick, and it is why interruption is free rather than something you have to code around.

The README also documents a from and to form in its very first snippet, which suggests both configurations are supported. That is worth noticing because it points at an API that has changed shape across versions, and the README does not reconcile the two forms or say which is preferred. If you are reading examples from a tutorial written against an older release, expect the from/to style; the jump-start section uses the target-value style.

Rendering goes through animated.* components. You cannot hand the returned style object to a plain div and expect it to animate, because the animated wrapper is what subscribes to the spring's per-frame updates. The README does not explain what happens if you pass the styles to a non-animated element, and the honest answer is that the documentation is silent on that failure mode.

## Installing @react-spring/web and animating a first component

The README gives the install command directly, and the package name encodes the renderer target. For a standard web app you install the web target.

```bash
npm install @react-spring/web
```

If your animation targets live in a react-three-fiber scene rather than the DOM, the README gives a different package for that case.

```bash
npm install @react-spring/three
```

With the web package installed, the README's jump-start component is the smallest real use. It imports animated and useSpring from @react-spring/web, calls useSpring with the current target values, and spreads the result onto an animated.div.

```jsx
import { animated, useSpring } from '@react-spring/web'

const FadeIn = ({ isVisible, children }) => {
  const styles = useSpring({
    opacity: isVisible ? 1 : 0,
    y: isVisible ? 0 : 24,
  })

  return <animated.div style={styles}>{children}</animated.div>
}
```

What you should see: toggling isVisible moves opacity between 0 and 1 and y between 24 and 0, and because the values are spring-driven rather than duration-driven, flipping the boolean mid-animation reverses the motion from its current position instead of restarting. The README describes this exact pattern as a scroll-in animation when the value is toggled, though the wiring of scroll position to that boolean is left to you.

The README points to the project site for further documentation and to per-page examples, including a codesandbox link for a card demo. It does not include a TypeScript setup note, a peer dependency range, or a note on server-side rendering, so if any of those matter to your build you will be reading the published package manifest rather than the README.

## Where spring physics is the wrong tool

The library's default is springs, and the README states that durations with easings are supported as well. That sentence is doing a lot of work, because the default is the thing most people will ship, and the default is not always what a design spec asks for.

Spring motion has no fixed end time. It settles. If a designer hands you a specification that says a panel opens in 300 milliseconds with a specific cubic-bezier curve, a spring will not reproduce that curve, and the tuning knobs you reach for are physical properties rather than the two control points the designer specified. You can get close, but you are now translating between two vocabularies, and every design revision reopens the translation. Teams with a strict motion specification and a review process that checks against it will find this friction constant rather than occasional.

There is a second boundary. The animated.* wrapper is a React component, so the animation lives inside React's render and commit cycle. Animating something that is not a React element, or coordinating an animation with a non-React imperative library on the same element, means working around that boundary. The README does not describe an escape hatch for this, and the imperative API it mentions in passing is not documented in the available excerpt.

A third consideration is bundle weight for trivial cases. If your entire need is a hover fade, the library is a heavier dependency than a CSS transition that the browser can run off the main thread. The README makes no performance claims, and none should be inferred from it.

## react-spring compared with Framer Motion and GSAP

The search data around this project is dominated by comparisons, so it is worth being concrete about the difference in approach rather than listing feature names.

Framer Motion, now commonly referred to as Motion, takes a declarative component-first approach: you wrap elements in motion components and describe states, and the library handles the transition between them. The overlap with react-spring is large, and the practical difference is where the animation model sits. react-spring's model is a simulated spring with physical parameters; the target-value retargeting behavior described above is the direct consequence. If your motion is driven by continuous input, a pointer position, a scroll offset, a drag delta, the spring model is the thing you want and the one you would otherwise have to build.

GSAP is a different animal entirely. It is a timeline-based animation engine with its own scheduling, not tied to React's render cycle, and it is commonly reached for when the work is a choreographed sequence with precise timing across many elements. If your problem is "these twelve things happen in this order over four seconds," that is a timeline problem, and a spring library is the wrong shape for it.

Against CSS animations, the difference is interruptibility and state coupling. CSS transitions handle simple state changes well and cost nothing in JavaScript. They handle a target that changes mid-flight less gracefully, and they cannot read a continuously updating value from React state without a style recalculation on every frame. That is the gap react-spring fills.

The repository's own dependency list is not evidence of quality, and the README's "Used by" section names Next.js, CodeSandbox and Aragon with a link to the dependents network. Treat that as a signal that the API is stable enough for production sites, not as a benchmark.

## Maintenance, release lines and the MIT licence

The repository is not archived, and its last push was on 2026-09-15, which is recent relative to the review date. The default branch is named next, which is worth noting: the branch you would clone by default is not named main, and the README's codesandbox link points at a main tree path, so the two do not line up.

The release situation deserves attention before you pin a version. The most recent releases listed are v11.0.0-beta.0 and v10.1.2, both published on 2026-06-24, with v10.1.1 preceding them on 2026-06-10. A beta and a stable patch landing on the same day means two lines are being maintained in parallel. If you install without a version range you may land on the stable 10.x line; if you deliberately opt into the 11 beta you are on a pre-release. The README does not describe a migration path between them, and it does not document rollback. The repository does carry a .changeset directory and a changeset-based release script, which indicates versioning and changelog entries are generated through Changesets rather than hand-written, so the changelog files in the published packages are where the upgrade detail actually lives.

Upgrade cost is therefore mostly a matter of reading changesets rather than following a migration guide, because none is offered in the README. The monorepo is a pnpm workspace with turbo, and the root package.json lists test scripts split into unit, types and e2e projects run through vitest. That structure tells you the project tests types explicitly, which is a reasonable signal for a TypeScript-first consumer, but it says nothing about whether your specific integration is covered.

The licence is MIT, declared in both the README's badge context and the root package.json license field. MIT permits commercial use and modification with the copyright notice retained. This is not legal advice; if your organisation has a policy on attribution or on bundled third-party notices, check the LICENSE file in the repository rather than this summary.

## Conclusion

Adopt react-spring when your interface needs interruptible, gesture-driven motion that a fixed keyframe timeline cannot express, and when you are already committed to React and can accept the hook API as the price of admission. Do not adopt it when you need a deterministic, frame-accurate timeline that a designer specifies in milliseconds, or when your animation targets are plain DOM nodes outside React, because the library is built around React's render cycle and its animated.* components. Before committing, verify three things against the repository: that the package you install matches your renderer (@react-spring/web for react-dom, @react-spring/three for react-three-fiber), that your React version satisfies the peer range in the published package manifest, and that the release line you pin is the one you actually intend to run, since the repository carries both a 10.1.2 line and an 11.0.0-beta.0 line published on the same day.

## FAQ

### How do I install react-spring?

Install the package for your renderer target. The README gives npm install @react-spring/web for react-dom and npm install @react-spring/three for react-three-fiber.

### How do I use react-spring in a component?

Import animated and useSpring from the renderer package, call useSpring with your target values, and pass the returned styles to an animated element such as animated.div. The README's jump-start example drives opacity and y from an isVisible boolean.

### What is react-spring?

It is a cross-platform, spring-physics first animation library for React, according to the README, which states that animations use springs by default and that durations with easings are also supported.

### What is react-spring/web?

It is the package target for react-dom. The README's install section lists npm install @react-spring/web as the command for the web target, and its examples import animated and useSpring from @react-spring/web.

### How does react-spring differ from Framer Motion?

The README describes react-spring as spring-physics first, so motion is simulated as a spring with a target that can be redirected mid-flight, rather than as a transition between declared states. Both are React animation libraries, and the README does not publish a direct comparison.

## Sources

- [License: MIT](https://github.com/pmndrs/react-spring/blob/next/LICENSE)
- [pmndrs/react-spring on GitHub](https://github.com/pmndrs/react-spring)
- [Project website](http://www.react-spring.dev/)
- [README](https://github.com/pmndrs/react-spring/blob/next/README.md)
- [Releases](https://github.com/pmndrs/react-spring/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pmndrs-react-spring
