# react-morph: one element becoming another with a hook and a spread operator

> A small TypeScript library whose whole API is a hook, two spread props and a one element rule, built on Popmotion popcorn and Wobble.

**brunnolou/react-morph** — Morphing Ui transitions made simple

- Repository: https://github.com/brunnolou/react-morph
- Website: https://brunnolou.github.io/react-morph
- Stars: 2,546 · Forks: 44
- Language: TypeScript
- License: not declared
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/brunnolou-react-morph

## The entire API surface in four lines

Install it, import one hook, and spread the result.

```sh
npm install react-morph
# or
yarn add react-morph
```

```js
const morph = useMorph(options);
```

Then the same spread goes on both the element that leaves and the element that arrives.

```jsx
<img {...morph} src="larva.png" width="50">
```

```jsx
<img {...morph} src="butterfly.png" width="80">
```

The README describes this as magically animating one element into another just by tagging the first and last state. Magic is doing some work in that sentence, but the mechanism underneath is not mysterious. The hook returns a set of props, and those props are what tell the library which DOM node to watch and how to measure it before and after the swap.

One requirement is set out in a blockquote, and it is the one that matters. Make sure you have just ONE element rendered at same time. That is not an incidental detail, it is the condition that lets the library pair an outgoing node with an incoming one and measure both.

## The simple example is four steps and a ternary

The README walks through what it calls the Simple Example in four steps: create two states as you normally would with HTML and CSS, call the useMorph hook, spread the elements you want to morph, and add and remove the element from the DOM.

The full component shows what that looks like in practice, and it is worth reading because the state handling is deliberately ordinary. A boolean toggle drives a ternary between two images, and both branches carry the same spread props. The component starts with the only import the library needs, React plus the hook itself, imported as useMorph from react-morph.

Note what is absent from that component. There is no duration parameter, no easing curve, no from state and no to state. The animation is inferred from the difference between how the element was rendered last time and how it renders now. That inference is the product, and it is also the limitation, since anything you would want to control explicitly is not exposed on this page.

## Four feature claims and what they imply

The features list is four bullets, and each one is a design decision rather than a marketing adjective.

Simplicity is the first, and the API above is the evidence. No hardcoded absolute positions is the second, and this is the interesting one. Morphing libraries that ask you to animate between two elements usually want start and end coordinates, which means your animation breaks the moment the layout changes, the viewport resizes, or the element moves because of content above it. Deriving the positions instead is what makes the result survive a responsive layout.

The third claim is that all props are GPU accelerated, and the fourth is that there is no layout or paint browser rendering. Together those two are the performance story. If the animation only ever touches transform and opacity, the browser can keep it on the compositor and never runs a layout pass. If that holds, a morph is cheap even while the main thread is busy, which is the difference between a nice effect and a page that stutters.

These are claims from the project rather than measurements published alongside it. There is no benchmark table and no reproduction instructions, so the reasonable approach is to open the library on a page you care about and watch the frame timings in your own devtools.

## Popmotion popcorn and Wobble do the actual work

The dependency list in package.json names two runtime libraries, both declared with loose lower bounds rather than pinned versions: @popmotion/popcorn at 0.3.1 or newer, and wobble at 1.5.1 or newer.

Popmotion popcorn is a library for generating procedural spring physics, which is a much better fit for interface transitions than a fixed duration curve, because a spring settles naturally and can be interrupted mid flight without a jump. Wobble is the package that generates the wobble path used for organic movement. Neither is a component library, so react-morph is not smuggling an opinionated styling system into your app.

The peer dependencies are the line that tells you which React versions work: React and its types at 16.8.0 or newer. That floor is not arbitrary, since hooks arrived in 16.8 and a library built around a hook cannot support anything older.

The runtime weight is the pleasant surprise here. Two small procedural animation packages and no framework dependency means adding this to an existing app should not disturb your bundle strategy in any meaningful way.

## A toolchain from the Babel 6 era

The devDependencies are where a careful reader should slow down, because they describe a project built several years ago and not continuously modernized.

The Babel stack is version 6: babel-cli at 6.26.0, babel-preset-react-app at 7.0.2, and separate plugins for class properties, object rest spread and regenerator. ESLint is configured through shared configs of the same vintage, including eslint-config-airbnb-base at 12.1.0, eslint-config-react-app at 2.1.0, eslint-config-prettier at 4.0.0 and eslint-config-skeleton, with a matching set of plugins such as jsx-a11y and prettier. Prettier sits at 1.9.2, and the TypeScript tooling is older too, with @typescript-eslint at 1.4.1.

Two observations follow. First, none of this affects you as a consumer, because you install the built package from npm and never run this toolchain. Second, it does describe the maintenance story. The test script is eslint src, which lints rather than runs tests, the build script is plain tsc with a dev mode of tsc watch, and the repository has no tagged releases even though package.json reads version 0.4.0. The npm badge in the README is therefore your only version signal.

The repository itself is small and tidy: a babelrc, an editorconfig, eslint and prettier configs, an npmignore, a src directory, a lib directory that is the published output, a docz directory for documentation, an examples directory holding two animated GIFs, and a tsconfig.json.

## Where the one element rule decides the fit

The single element constraint is what decides whether this library belongs in your component, so it is worth thinking through what it rules out.

Because the library has to pair an outgoing node with an incoming one, there is no such thing as morphing a list of five items into a different five items, and no way to morph a container and its children at the same time. If your interface swaps a thumbnail grid for a detail view, this is not the tool. If it swaps one image for another, changes a button label, or replaces a small preview with a larger one, it is exactly the right shape of problem.

The other thing to know is where the documentation lives. The README defers to a separate site at brunnolou.github.io/react-morph, and everything on this page is enough to get started while everything about options, edge cases and API reference is somewhere else. Two live demos are linked as CodeSandboxes, a hello world and an Apple App Store recreation, and the second is the fastest way to see whether the effect reads as considered in your own visual language before you add the dependency.

The last push was on 2026-08-20, with 2,546 stars, 44 forks and 27 open issues, and the repository is not archived.

## Conclusion

react-morph is one of those libraries that solves a problem you did not know you had until you saw a demo, and the API is small enough to hold in your head in a minute. You tag the departing element and the arriving element with the same hook output, and the library works out the in between geometry so you never write a single absolute coordinate. The constraint it imposes, one element at a time, is what makes that possible, and the README states it as a requirement rather than a preference. The honest limitations sit in package.json rather than in the README. The peer dependency asks for React 16.8 or newer, the toolchain is Babel 6 era with ESLint configs from the same period, the declared test script only lints the source, and there are no tagged releases even though the package version reads 0.4.0. Read those as signs of a project in maintenance mode rather than one in active development. The last push was on 2026-08-20 and the repository is not archived, so it works, but pin the version and expect to read the source if you need something the hook does not cover.

## FAQ

### What is react-morph and how do I use it?

react-morph animates one element into another by tagging both states with the same hook output. You install the package, call useMorph inside your component, and spread the returned props onto the element that leaves and the one that arrives. The README requires that only one element be rendered at a time.

### Which React versions does react-morph support?

React 16.8.0 or newer, which is also the floor for the @types/react peer dependency. That minimum exists because the library is built around a hook, and hooks did not exist before 16.8.

### Does react-morph need me to hardcode element positions?

No. One of the four stated features is that there are no hardcoded absolute positions, so the transition is derived from how the element was rendered before and after rather than from coordinates you supply. That is what lets the effect survive a responsive layout or a content change above the element.

### What does react-morph depend on at runtime?

Two small procedural animation libraries and nothing else. @popmotion/popcorn at 0.3.1 or newer provides spring physics, and wobble at 1.5.1 or newer generates organic wobble paths. There is no component or styling dependency, so adding it to an existing React app should not reshape your bundle strategy.

### Can react-morph animate a list of items changing?

Not in the way it is designed to work. The README requires exactly one element rendered at a time, because the library pairs the departing node with the arriving one to measure both. It suits one for one swaps such as a small image becoming a large one, not a grid turning into a detail view.

## Sources

- [brunnolou/react-morph on GitHub](https://github.com/brunnolou/react-morph)
- [Issues](https://github.com/brunnolou/react-morph/issues)
- [Project website](https://brunnolou.github.io/react-morph)
- [README](https://github.com/brunnolou/react-morph/blob/master/README.md)

---

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