renature
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
renature is a physics-based animation library for React inspired by the natural world. It takes inspiration from the physics of our universe and models real-world forces like gravity, friction, air resistance, and fluid dynamics.
renature is influenced by other popular physics-based animation libraries like react-spring and elements of Framer Motion. In this article, we will learn how to create animations in renature by building several examples and applications.
First things first: install the renature package by running either of the commands below:
npm install --save renature # or yarn add renature
renature provides six hooks we can use in creating animations. The first three are the useFriction, useGravity, and useFluidResistance hooks. These hooks animate the properties of a single element.
They all work in a similar manner, but as their names imply, the natural forces they are modeled after and the physics they depend on will vary. Also, the config object varies from hook to hook depending on the force you’re using.
There are also the useFrictionGroup, useGravityGroup, and useFluidResistanceGroup hooks, which we can use to animate the properties of a group of elements simultaneously.
Let’s take a look at a basic implementation using the useFriction hook.
import { useFriction } from "renature";
import "./styles.css";
export default function App() {
const [props] = useFriction({
from: {
transform: "translateX(100px)"
},
to: {
transform: "translateX(300px)"
},
config: {
mu: 0.2,
mass: 20,
initialVelocity: 5
},
repeat: Infinity
});
return (
<div className="App">
<div {...props} className="box" />
</div>
);
}
If you’re familiar with react-spring, you’ll notice renature has a similar declarative API. Every hook in renature expects from and to properties wherein we describe the CSS states we want to animate to and from. If a value is set for a property in from but not in to, that property will be ignored, and it won’t be animated.
Sometimes we might want our animations to run infinitely without stopping. We can do this by applying repeat: Infinity to our animation configuration.
Since we used the useFriction hook, we pass in the corresponding physics parameters. As its name indicates, the useFriction hook is modeled after the frictional force. The parameters are:
mu, which is the coefficient of kinetic frictionmass, which is the mass of the moving bodyinitialVelocity, which is the initial velocity of the movementThe wonderful thing about renature is that you don’t need to be a science guru to make awesome animations.
renatureSo we’ve seen how the useFriction hook works and how to animate the transform property of an element. Where and when necessary, we can also animate multiple properties simultaneously:
import { useFriction } from "renature";
import "./styles.css";
export default function App() {
const [props] = useFriction({
from: {
transform: "translateX(100px), rotate(0deg)",
background: "red",
borderRadius: "0%"
},
to: {
transform: "translateX(300px), rotate(360deg)",
background: "steelblue",
borderRadius: "50%"
},
config: {
mu: 0.2,
mass: 20,
initialVelocity: 5
},
repeat: Infinity
});
return (
<div className="App">
<div {...props} className="box" />
</div>
);
}
To animate multiple properties of an element, add more properties of the element you want to animate.
We may want to animate multiple elements simultaneously. Grouped animations allow us to specify the number of elements to animate and to set the configuration for each of them independently.
import { useGravityGroup } from "renature";
import "./styles.css";
export default function App() {
const [nodes] = useGravityGroup(3, (i) => ({
from: {
transform: "translate(0px, 0px) scale(1) skewY(0deg)",
opacity: 0
},
to: {
transform: "translate(20px, 20px) scale(1.2) skewY(5deg)",
opacity: 1
},
config: {
moverMass: 10000,
attractorMass: 10000000000000,
r: 10
},
repeat: Infinity,
delay: i * 500
}));
return (
<div className="App">
<div className="container">
{nodes.map((props, i) => {
return <div className="box" key={i} {...props} />;
})}
</div>
</div>
);
}
We used the useGravityGroup to create the grouped animation above. All grouped animation hooks take a similar form:
const [props] = use<Force>Group(n: number, fn: (index: number) => Config);
You can delay animations in renature by specifying the delay property in your animation configuration. delay expects a number in milliseconds and will start the animation once the specified delay has elapsed. This is most commonly used for grouped animations where you want to stagger children animating at regular intervals.
Let’s look at how to control animation states in renature. We can start, stop, pause, delay, and run animations a specific number of times. We’ve already looked at how to repeat and delay animations.
To control animation states, renature provides a controller API. The controller is the second object returned by a renature hook, and it comes with three methods — start, pause, and stop — for interacting with your animation’s play state.
import { useFriction } from "renature";
import "./styles.css";
import Button from "./Button";
export default function App() {
const [props, controller] = useFriction({
from: {
transform: "translateY(0px)",
opacity: 1,
borderRadius: "10%"
},
to: {
transform: "translateY(50px)",
opacity: 0,
borderRadius: "50%"
},
repeat: Infinity,
pause: true //Signal that the animation should not run on mount.
});
return (
<div className="App">
<div className="btn-box">
<Button action={controller.start} text="start" />
<Button action={controller.pause} text="pause" />
<Button action={controller.stop} text="stop" />
</div>
<div className="box" {...props} />
</div>
);
}
We use the controller.start method available in the controller API to start animations. To do so, we need to include pause: true in our animation configuration to prevent the animation from immediately running on mount.
To pause a running animation, we use the controller.pause method. This method will stop the frame loop but preserve the animation state. This means we can resume an animation at any time using controller.start.
To stop a running animation, we use the controller.stop method. Unlike controller.pause, this will destroy the animation state, so we should only use it when we’re certain we want the animation to end.
renature demosNow that we know how to create animations with renature, let’s look at some real-world applications.
Have you ever hovered over a card and seen the image scale up or down along the width and height of its container? Let’s recreate that effect using renature.
Here’s a sandbox of what we will build:
While CSS is not the focus of this topic, we will need some CSS for the effect to work as it should.
.card-box {
width: 200px;
border: 1px solid grey;
overflow: hidden;
margin-bottom: 2rem;
}
We have to set an overflow: hidden to the card-box div to ensure that when the image scales, it remains restricted in its container. With that explained, let’s get back to renature.
Using what we learned about controlling animation states, we call the controller.start and the controller.pause methods whenever we hover in and out of the img-box div.
export default function Card({ imgUrl, props, controller }) {
return (
<div className="card-box">
<div
className="img-box"
{...props}
onMouseEnter={controller.start}
onMouseLeave={controller.pause}
>
<img src={imgUrl} />
</div>
//more stuff below...
Notification toasts are among the common components used in applications. Let’s create one using renature.
Here’s a sandbox of what we will build:
For the CSS, we had to set the opacity of the toast to 0 so that it only shows when the button is clicked.
return (
<div className="App">
<button onClick={controller.start}>show toast</button>
<Toast props={props} />
</div>
);
We call the controller.start method when the button is clicked. This fires the animation, and the toast drops down into view.
Not everyone enjoys decorative animations or transitions, and some users experience outright mental and physical difficulty when faced with parallax scrolling, zooming effects, and so on. As such, we must ensure that our animations don’t present accessibility concerns for application users.
In renature‘s config object, we can include an optional reducedMotion property. If this is left unspecified and the end user prefers reduced motion, the animating element will be set immediately to the to state to avoid potentially harmful animation.
While I’ve made some demos in this article showing how renature can be used in real-world applications, it would be awesome if the team included more practical demos that might be useful to developers. Regardless, renature is an awesome physics-based animation library.
Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not
server-side
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>

Promise.all still relevant in 2025?In 2025, async JavaScript looks very different. With tools like Promise.any, Promise.allSettled, and Array.fromAsync, many developers wonder if Promise.all is still worth it. The short answer is yes — but only if you know when and why to use it.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 29th issue.

Learn about the new features in the Next.js 16 release: why they matter, how they impact your workflow, and how to start using them.

Test out Meta’s AI model, Llama, on a real CRUD frontend projects, compare it with competing models, and walk through the setup process.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now