Diagnosing Framer Motion Layout Animation Glitches in React
Layout animations that jump, snap back, or stutter usually come down to unstable keys, CSS conflicts, or too many simultaneous measurements. A diagnostic guide with ordered checks and matched fixes.
22 May 2026, 16:29 UTC

Your list re-renders, items jump instead of gliding, or an element snaps back to its old position mid-animation. These are the classic symptoms of Framer Motion's layout prop breaking down — and they almost always trace back to one of a handful of causes: unstable keys, conflicting CSS, too many simultaneous layout measurements, or competing transform sources. This guide walks through the conditions, the checks that identify each cause, and the fix that matches what you find.
Assumptions: Framer Motion 10/11 (the framer-motion package; the newer motion package behaves the same for these APIs) and React 18. Verify against your installed version with npm ls framer-motion.
Recognizable conditions
- No animation at all: an item is added, removed, or reordered and it just appears in its new spot.
- Snap-back or double-jump: the element animates, then teleports to a different position at the end.
- Stutter under load: animations are fine with three items but choppy with thirty.
- Draggable element fights its own layout: a card you can drag also participates in layout animation and the two transforms conflict.
Cause and diagnostic table
| Symptom | Likely cause | Quick check |
|---|---|---|
| No animation on reorder | Unstable or index-based key | React DevTools: does the key change across renders? |
| Snap-back at animation end | CSS margin/padding/transform fighting computed layout | Set layout={false}; does the jump disappear? |
| Choppy with many items | Too many simultaneous layout measurements | Performance panel: frames over ~16ms during animation |
| Drag + layout conflict | Both drag and layout writing transforms | Disable one prop; does the other behave? |
| Animation resets mid-way | Variants targeting the same property without a transition | Console warnings about conflicting values |
Ordered checks
1. Confirm keys are stable
Layout animations are tracked by React key. If you key list items by array index, reordering changes which key maps to which data, and Framer Motion treats items as unmounted/remounted instead of moved. In React DevTools, select an item and re-trigger the reorder — the key should stay attached to the same logical item.
// Bad: key changes meaning when the list reorders
{items.map((item, i) => <motion.li layout key={i}>{item.name}</motion.li>)}
// Good: key follows the data
{items.map((item) => <motion.li layout key={item.id}>{item.name}</motion.li>)}The same applies to conditionally rendered children: wrap enter/exit cases in AnimatePresence and give each branch a stable key, or the layout system sees an unmount rather than a transition.
2. Isolate with layout={false}
This is the fastest binary diagnostic. Temporarily set layout={false} on the suspect element. If the glitch disappears, the problem is in layout measurement or a CSS conflict, not in your variants or state logic. Re-enable with the minimal child tree and add children back until the glitch returns — that child is your conflict source.
3. Look for CSS that fights the layout projection
Framer Motion animates layout by measuring before/after bounding boxes and applying a compensating transform. Anything that also shifts the box during the animation — animated margins, padding changes, a parent transition on width, or a CSS transform on the same element — produces a snap-back at the end. Check DevTools' Styles panel for transitions on box properties, and prefer animating with Framer Motion values (or layout itself) rather than CSS transitions on the same properties.
4. Profile frame time
Every element with layout triggers DOM reads (measurement) and writes (transform) each time the tree changes. With dozens of elements, this exceeds the ~16ms frame budget. In Chrome DevTools, record the Performance panel while triggering the animation and look at frame durations during it. If frames spike, reduce the surface area: only put layout on the elements that actually move, and consider layout="position" where size doesn't change — it skips size measurement and is cheaper.
5. Check drag/layout and variant conflicts
Combining drag and layout on one element means two systems writing transform. If you need both, constrain the drag so the layout projection isn't fighting an unbounded gesture:
<motion.div
layout
drag="x"
dragConstraints={{ left: 0, right: 0 }}
dragElastic={0.2}
/>For variants, if two active variants define the same property, the last-applied wins and transitions can reset. Consolidate the property into one variant or give it an explicit transition so the handoff is defined.
Fixes tied to findings
- Unstable key: switch to a data-derived ID; wrap conditional branches in
AnimatePresencewith stable keys. - CSS conflict: remove CSS transitions on box/transform properties for animated elements; let Framer Motion own those properties.
- Overflow clipping: avoid
layouton elements withoverflow: hiddenunless clipping is the intent — it forces re-measurement of clipped content and can thrash. Movelayoutto an inner wrapper. - Too many layout elements: scope
layoutto movers only, uselayout="position", or virtualize long lists. - Drag conflict: add
dragConstraints/dragElastic, or separate concerns — drag a handle, animate layout on the container.
Escalation criteria
Move beyond local fixes when: (1) the glitch persists with layout on a single isolated element in a fresh sandbox — that suggests a version bug, so check the changelog and open a minimal reproduction issue; (2) frame times stay high after scoping — the design may need virtualization or a FLIP-free approach; (3) the conflict comes from a third-party component injecting its own transforms — wrap it in a plain <div> and animate the wrapper instead.
Verifying the fix
Re-run the interaction with the Performance panel recording: frames during the animation should stay near or under 16ms, and the element's final computed position in DevTools should match its resting layout with no trailing transform. Then re-enable any props you disabled during isolation one at a time to confirm the fix holds under the full configuration.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.