FLIP Animations and React: A Practical Guide
The Web Animations API (WAAPI) is now supported, without flags, in all modern browsers. That makes it an attractive option for JavaScript-driven animation: it's native, so no libraries are required. But WAAPI is only half the story. The FLIP technique is one of the most efficient ways to animate layout changes, and it pairs remarkably well with WAAPI—and with React.
Why FLIP With WAAPI Is Better Than the Old Way
The core idea of FLIP is to position an element at its final destination first, then apply transforms to move it back to its starting point, and finally unapply those transforms to trigger the animation. Since transforms are cheap to animate, FLIP is very performant.
Before WAAPI, this required directly manipulating element styles and waiting for the next frame to invert the transform:
// FLIP Before the WAAPI
el.style.transform = `translateY(200px)`;
requestAnimationFrame(() => {
el.style.transform = '';
});
That approach works, but it comes with several significant problems:
- The code feels like a hack.
- Reversing animations is extremely difficult. Starting a new FLIP while a previous one is running causes glitches.
- Advanced effects, like counter-scaling a parent's children to prevent distortion, require parsing the transform matrix each frame.
- There are browser gotchas; for instance, Firefox sometimes needs
requestAnimationFramecalled twice to get a FLIP working correctly:
requestAnimationFrame(() => {
requestAnimationFrame(() => {
el.style.transform = '';
});
});
WAAPI eliminates all of these headaches. The reverse function handles reversing smoothly. Counter-scaling children is possible. And debugging is easier because you're working with simple calls like animate and reverse instead of digging through requestAnimationFrame loops.
Here's the general structure of a WAAPI-based FLIP animation:
el.classList.toggle('someclass');
const keyframes = /* Calculate the size/position diff */;
el.animate(keyframes, 2000);
Bringing FLIP Into React
To use FLIP in React, you must understand a specific lifecycle moment. The magic of FLIP is that an element gets transformed before the browser paints. In vanilla JavaScript, you control that timing directly. In React, the DOM updates are handled for you, so you need a hook that fires at the right time.
That hook is useLayoutEffect. This is exactly what it was designed for: callbacks passed to it run synchronously after DOM mutations but before the browser paints. It's the ideal place to set up a FLIP animation.
Consider the anatomy of a FLIP animation step-by-step:

Everything highlighted in purple must happen before the paint step, or the user will see a momentary flash of unstyled content. In a React component, work often spans two renders, which makes the timeline harder to read from code alone. The order of operations looks like this:

This two-render structure is why you need to cache the element's position and size after the first render, typically inside useEffect. On the second render, useLayoutEffect runs after all DOM updates, so you won't have a chance to capture the starting data then. The procedure mirrors vanilla FLIP, but with React's rendering cycle dictating where each piece of logic belongs.
Caveats and Best Practices
FLIP animations in React are powerful, but they bring their own set of constraints.
Keep It Under 100ms
FLIP involves computation, and all that work must complete before the browser can render a smooth 60fps transform. If the total calculation stays under 100ms, users won't notice the delay. Use the Performance tab in DevTools to verify you're within the limit.

Avoid Unnecessary Renders
Do not use useState to cache positions, sizes, or animation objects. Every setState triggers an extra render, which slows things down and can introduce bugs. Use useRef instead; treat it as a mutable cache that won't cause re-renders.
Watch for Layout Thrashing
Repeatedly reading layout properties like getBoundingClientRect and then immediately triggering an animation forces the browser to recalculate layout over and over. Batch your reads and writes to keep animations smooth.
Manage Animation Cancellation
If a user interacts with an element mid-animation, you'll see glitches. Not every scenario can be handled with the reverse function—sometimes you need to stop an animation and move to a new position entirely. In that case, you must:
- Get the moving element's current position and size.
- Finish its current animation.
- Calculate the new differences in position and scale.
- Start a fresh animation.
This is harder in React than it sounds. You'll want to cache the current animation object, perhaps in a Map keyed by element ID. To obtain the moving element's dimensions, you have two options:
- Function component: Loop through each animated element in the component body and cache the current positions.
- Class component: Use the
getSnapshotBeforeUpdatelifecycle method, which React docs recommend for this purpose because of potential delays between the render and commit phases.
There is no hook equivalent to getSnapshotBeforeUpdate yet; for function components, reading positions in the component body works well enough.
Don't Fight the Browser
FLIP is not a universal solution. If you just need to animate a simple size change, standard CSS with transform: scale() is often the better choice. Save FLIP for situations where the browser simply can't help, such as:
- Animating an element's position when it moves somewhere else in the DOM.
- Shared layout transitions, where two different DOM elements look like a single element changing position as one is hidden and another is shown.
Existing Libraries
Several actively maintained libraries abstract away the boilerplate if you prefer not to build FLIP from scratch. Options include react-flip-toolkit and react-easy-flip. For a broader animation tool that also handles shared layout transitions, framer-motion is a heavier but capable alternative.



