Animating React List Reorders with Framer Motion's layout Prop: A Practical Walkthrough
Framer Motion's layout prop turns abrupt React list reorders into smooth transform-based slides with one attribute and a stable key — here's a worked example and the performance considerations to watch.
22 Feb 2026, 14:51 UTC

The jarring jump problem
Sort a todo list, filter a table, or move a card to the top of a feed, and React does exactly what you asked: it re-renders the DOM in the new order instantly. The problem is that "instantly" reads as a jump. Items teleport. The user loses track of the thing they were looking at, and the interface feels cheaper than it is.
The useful takeaway: Framer Motion's layout prop animates these position and size changes automatically, using CSS transforms, with essentially one attribute per item. You keep your normal React state updates; the library handles the visual continuity.
What layout actually does
When you add layout to a motion component, Framer Motion measures the element's bounding box before and after each render where it changed. It then applies a transform that makes the element appear to stay in its old position, and animates that transform to zero. The element ends up in its new spot, but the user sees a slide instead of a snap.
This is a variant of the FLIP technique (First, Last, Invert, Play), a well-established approach for animating layout changes without animating properties like top or width that trigger expensive browser reflows. Because the visible motion runs on transform, it stays on the compositor thread and is generally smooth.
Two things matter for correctness:
- Stable keys. Each animated element needs a stable React
keytied to the item's identity (an id, not the array index). Without it, Framer Motion can't match elements across renders and you'll see glitches or no animation at all. - Don't measure in render. Avoid reading DOM values like
offsetHeightduring the render cycle to compute positions yourself. That causes layout thrashing and fights the library's own measurement pass. Let it measure internally.
A worked example: move-to-top todo list
Here's a minimal list where clicking an item moves it to the top, and the rest slide down to make room. This is standard Framer Motion usage for React 17/18 with framer-motion v6+; the API has been stable across recent major versions, but check the docs for your installed version.
import { useState } from "react";
import { motion } from "framer-motion";
const initial = [
{ id: "a", text: "Buy milk" },
{ id: "b", text: "Write report" },
{ id: "c", text: "Call dentist" },
{ id: "d", text: "Water plants" },
];
export default function TodoList() {
const [items, setItems] = useState(initial);
const moveToTop = (id) =>
setItems((prev) => {
const item = prev.find((i) => i.id === id);
return [item, ...prev.filter((i) => i.id !== id)];
});
return (
<ul style={{ listStyle: "none", padding: 0 }}>
{items.map((item) => (
<motion.li
key={item.id}
layout
onClick={() => moveToTop(item.id)}
style={{ padding: 12, border: "1px solid #ccc", marginBottom: 8 }}
>
{item.text}
</motion.li>
))}
</ul>
);
}That's the whole trick. The state update is an ordinary immutable array reorder; layout plus the stable key turns the resulting DOM change into an animated shuffle. If you also add or remove items, wrap the list in <AnimatePresence> and give items initial/exit props so enter/exit animations compose with the reorder.
To verify it's working as intended, open your browser's devtools, enable paint flashing or the performance panel, and click an item. You should see the reorder produce transform changes on the li elements rather than layout recalculations across the whole list. You can also inspect an element mid-animation and watch its inline transform style update.
The trade-off: measurement isn't free
Every render that could change positions triggers Framer Motion to measure the affected DOM nodes. For a handful of items, or even a couple hundred, this overhead is typically negligible. For very large lists — the library's documentation suggests considering virtualization when rendering hundreds of animated rows simultaneously — the measurement pass plus the sheer number of animating elements can drop frames, especially on lower-end devices.
Practical mitigations, roughly in order of effort:
- Memoize item components (
React.memo) so unrelated re-renders don't trigger extra measurement work. - Keep state updates tidy. A
useReducerwith clear actions (move, remove, insert) makes reorder logic easy to reason about and avoids accidental double renders. - Virtualize long lists. If only 30 rows are visible, only 30 rows exist in the DOM, so only 30 get measured and animated. Pairing
layoutwith a virtualization library works, but test carefully — items entering the viewport during a scroll can animate in surprising ways. - Scope the animation. If the list is huge, consider animating only the moved item and its immediate neighbors, or skipping animation for offscreen changes entirely.
A quick way to find your limit: render the list with a few hundred items, trigger a reorder, and record a performance profile in devtools. If scripting and rendering stay well under a frame budget (~16ms at 60fps), you're fine. If not, virtualize before reaching for anything fancier.
When to reach for it
Use layout for moderate-sized, interactive lists where order changes carry meaning: kanban columns, sortable queues, filtered galleries, priority lists. The code cost is one prop and a stable key; the perceived-quality gain is large. Skip it — or virtualize first — when you're rendering hundreds of rows at once, and always verify with a performance profile on a real device rather than assuming. Start with the todo-list example above, confirm the transforms in devtools, then scale up until you find where your own UI needs the optimization.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.