mrkeyoor.com_
Wed 23 Sept 00:32 UTC
npmWeb Frontendupdated 22 Sept 2026

@react-spring/web review

@react-spring/web 10.1.2 animates React DOM and SVG values with spring physics, while its `animated` components apply frame updates outside normal React renders. Hooks cover one spring, item collections, enter and leave transitions, trails, scroll values, and imperative controllers. The current patch fixes missing TypeScript keys when `from` is partial, changes SpringValue `onChange` to receive AnimationResult, and caches numeric parsing for large string interpolations. Our full-package browser import measured 24.3 KB gzipped.

Verdict

@react-spring/web 10.1.2 installed in 3.4 seconds as 10 packages using 10 MB, produced a 24.3 KB gzipped full import, and had 0 audit findings in our sandbox. Choose it when interruptible spring motion or value-driven interaction earns that runtime; use CSS for ordinary fades and fixed transitions.

We installed it

Lab card: what happened when we installed @react-spring/webScreenshot of @react-spring/web documentation
Install✓ · 3.4s10 packages on disk · 10 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser24.3 KBgzipped (63.1 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does @react-spring/web install cleanly?

Yes. In a fresh container with an empty cache, npm install @react-spring/web finished in 3 seconds, leaving 10 packages and 10 MB on disk. npm audit reported no known vulnerabilities.

How much does @react-spring/web add to a browser bundle?

24.3 KB gzipped (63.1 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does @react-spring/web work with both ESM and CommonJS?

Yes. Both import '@react-spring/web' and require('@react-spring/web') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does @react-spring/web include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

@react-spring/web or motion: which should you use?

motion: Use it for declarative variants, layout animation, gestures, and scroll features within a broader component API. @react-spring/web 10.1.2 installed in 3.4 seconds as 10 packages using 10 MB, produced a 24.3 KB gzipped full import, and had 0 audit findings in our sandbox.

When should you not use @react-spring/web?

You need a couple of opacity or transform transitions. CSS avoids the 24.3 KB gzipped full-package import we measured.

API stability4/5The library still centers on `animated`, `useSpring`, `useSprings`, `useTrail`, `useTransition`, SpringValue, and controller refs across the 10.x line. Version 10.1.2 repairs inferred style keys and standardizes SpringValue `onChange` around AnimationResult, which can require callback edits. Its peer range spans React 16.8 through 19, reducing framework-version churn.
Docs4/5The official site gives dedicated pages and demos for each hook, spring configuration, events, TypeScript, testing, reduced motion, interpolation, and imperative control. It documents the object and function overloads and the testing delay. Similar hooks return different shapes, and reference tables can hide the lifecycle consequence of choosing useSprings instead of useTransition.
Maintenance5/5npm published 10.1.2 on 2026-06-24, and GitHub shows the unarchived repository pushed on 2026-08-19 with 74 issues and pull requests in the repository counter. The release fixed TypeScript inference, SpringValue event payloads, a React Native wrapper crash, and string interpolation performance. Active web, native, and three targets share the monorepo.
Ecosystem5/5npm counted 5,501,107 downloads in the latest completed week, and GitHub reports 29,138 stars. The web target supports React 16.8 through 19, publishes import and require paths with bundled declarations, and shares primitives with React Native and React Three Fiber packages. The 24.3 KB gzipped full-import measurement is a real cost in smaller interfaces.

Use it if

  • Interaction can change its destination mid-flight, so velocity-preserving spring motion fits better than a fixed keyframe.
  • A React list, route, or dialog needs enter and leave states that survive long enough to animate removed items.
  • Drag, hover, scroll, or gesture input should drive values continuously without rerendering React for every frame.
  • One typed API should animate DOM styles, SVG attributes, colors, transforms, and derived values.
Skip it if

Setup reality

We installed @react-spring/web 10.1.2 in a fresh Node 22 Bookworm sandbox. npm took 3.4 seconds and left 10 packages using 10 MB on disk. The package is 100 KB unpacked, declares 5 direct dependencies and 2 peers, and includes TypeScript declarations under MIT. npm audit found 0 known vulnerabilities.

The package is CommonJS with an exports map, and both require() and ESM import worked in our checks. Its peers accept React and React DOM 16.8 through 19. A full import * browser build measured 63.1 KB minified and 24.3 KB gzipped. Import only the hooks and animated target you use, then inspect the production bundle rather than treating that full-import figure as every app's final cost.

Spring values belong on animated.div, animated.svg, or another animated target. The object form of useSpring() returns styles; the function form with dependencies can return [styles, api]. Version 10.1.2 fixes type inference when a partial from object omitted keys that still animate through to, and SpringValue-level onChange now receives an AnimationResult with its live value under result.value.

Removed React items need useTransition() so they remain mounted through leave. Server-component frameworks need a client boundary around hook usage. Call useReducedMotion() near the application root to synchronize the library's global skip setting. Tests that set Globals.assign({ skipAnimation: true }) must still flush the scheduled React update before asserting final styles.

Patterns

Animate from a boolean prop animate-state

import { animated, useSpring } from "@react-spring/web"

function Panel({ open }) {
  const styles = useSpring({
    opacity: open ? 1 : 0,
    y: open ? 0 : 16,
  })

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

SpringValue fields must reach an `animated` element; a plain div does not subscribe to their frame updates.

Start motion from an event imperative-control

import { animated, useSpring } from "@react-spring/web"

function Nudge() {
  const [styles, api] = useSpring(() => ({ x: 0 }), [])

  return (
    <animated.button
      style={styles}
      onClick={() => api.start({ from: { x: 0 }, to: { x: 20 } })}
    >
      Nudge
    </animated.button>
  )
}

The function form with a dependency list provides the `[styles, api]` tuple. A plain configuration object returns only styles.

Tune spring physics spring-config

const styles = useSpring({
  from: { opacity: 0, scale: 0.9 },
  to: { opacity: 1, scale: 1 },
  config: { mass: 1, tension: 210, friction: 20 },
})

Physics-based springs do not have a fixed duration. Use `config.duration` only when the specification is time-based.

Configure each property separately per-key-config

const styles = useSpring({
  from: { opacity: 0, y: 30 },
  to: { opacity: 1, y: 0 },
  config: key => key === "opacity"
    ? { duration: 180 }
    : { tension: 220, friction: 22 },
})

Easing applies to duration animations. The config callback receives each animated key and can mix timing models.

Keep leaving list items mounted transition-list

import { animated, useTransition } from "@react-spring/web"

const transitions = useTransition(items, {
  keys: item => item.id,
  from: { opacity: 0, height: 0 },
  enter: { opacity: 1, height: 40 },
  leave: { opacity: 0, height: 0 },
})

return transitions((style, item) => (
  <animated.div style={style}>{item.label}</animated.div>
))

Stable keys let `useTransition()` retain removed items until their leave animation finishes.

Create an independent spring per row animate-collection

const [springs, api] = useSprings(
  rows.length,
  index => ({
    from: { opacity: 0, y: 12 },
    to: { opacity: 1, y: 0 },
    delay: index * 40,
  }),
  [rows.length],
)

return springs.map((style, index) => (
  <animated.div key={rows[index].id} style={style}>
    {rows[index].label}
  </animated.div>
))

The spring count and item array must stay aligned. Use `useTransition()` when rows are inserted and removed by identity.

Make each spring follow the previous one trail-items

const trail = useTrail(labels.length, {
  from: { opacity: 0, x: -12 },
  to: { opacity: 1, x: 0 },
})

return trail.map((style, index) => (
  <animated.span key={labels[index]} style={style}>
    {labels[index]}
  </animated.span>
))

`useTrail()` couples each spring to its predecessor. Use `useSprings()` when destinations and timing must be independent.

Map one spring into a transform derive-value

const { progress } = useSpring({
  from: { progress: 0 },
  to: { progress: 1 },
})

return (
  <animated.div
    style={{
      transform: progress.to(value => `scale(${0.8 + value * 0.2})`),
    }}
  />
)

Use `.to()` for a subscribed derived value. Calling `.get()` during render only reads the current snapshot.

Read the v10.1.2 onChange payload handle-change

const value = new SpringValue(0)

value.start({
  to: 1,
  onChange: result => {
    console.log(result.value, result.finished, result.cancelled)
  },
})

At SpringValue level, version 10.1.2 passes AnimationResult. Code written for the old raw-value callback must read `result.value`.

Act only after a completed spring rest-callback

const styles = useSpring({
  opacity: visible ? 1 : 0,
  onRest: result => {
    if (result.finished && !result.cancelled) {
      markSettled()
    }
  },
})

An interrupted animation can still invoke event callbacks. Check `finished` and `cancelled` before advancing application state.

Follow the system motion preference reduced-motion

import { useReducedMotion } from "@react-spring/web"

function App() {
  useReducedMotion()
  return <Routes />
}

The hook updates React Spring's global skipAnimation setting. Mount it near the root so controllers share one preference.

Skip animation frames in tests test-final-state

import { Globals } from "@react-spring/web"

beforeEach(() => {
  Globals.assign({ skipAnimation: true })
})

afterEach(() => {
  Globals.assign({ skipAnimation: false })
})

The final React update is still scheduled. Await `waitFor()` or advance configured fake timers before reading styles.

Alternatives

PackageRegistryPick it when
motionnpmUse it for declarative variants, layout animation, gestures, and scroll features within a broader component API.
react-transition-groupnpmUse it when you only need enter and exit lifecycle states and prefer to own the CSS.
@motionone/reactnpmUse it for a smaller React layer built around duration-driven Web Animations API behavior.

More web frontend guides

postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.