Using Framer Motion layout animations for shared element transitions in React
Learn the minimal setup, data boundaries, and checks needed to safely animate elements across routes with Framer Motion's layout feature.
29 Jun 2026, 12:55 UTC

Requirements
To enable a layout animation that moves an element from one route to another (or between views) you must:
- Wrap the changing tree in
<AnimatePresence>so Framer Motion knows when a component is removed. - Give the element a stable
layoutIdthat does not change while the animation is in flight. - Add the
layoutprop (orlayout={{ type: "layout" }}) to the motion component so Framer Motion reads its DOM layout (position, size) and animates the change. - Define an exit animation (e.g.,
exit={{ opacity: 0, scale: 0.95 }}) insideAnimatePresenceto animate the leaving element.
No extra React state is required for the animation itself; the motion values are driven purely by layout reads.
Smallest suitable design
The following snippet shows the minimal configuration for a shared‑element transition between two pages that render the same component (e.g., a product card).
import { motion, AnimatePresence } from "framer-motion";
import { useLocation } from "react-router-dom";
function ProductCard({ product }) {
return (
{product.name}
${product.price}
);
}
function ProductList() {
const location = useLocation();
// Assume `data` is fetched elsewhere (React Query, SWR, etc.)
return (
{data.map(product => (
))}
);
}
export default ProductList;
Only the layoutId, layout prop, and the wrapper AnimatePresence are required.
Trust and data boundaries
Framer Motion’s layout animation reads the element’s bounding box and transform matrix directly from the DOM. It does not:
- Access React context, Redux, or any external store.
- Read props other than those that affect layout (size, position).
- Modify data fetching logic.
Therefore it is safe to use with data‑fetching libraries as long as the layoutId remains tied to the same logical item. If the data changes the item’s identity (e.g., a new ID is generated), you must keep the layoutId stable (perhaps by using a slug or database ID) otherwise the animation will be aborted.
Operational checks
- Stable layoutId – Verify that the string passed to
layoutIddoes not change between renders while an animation is active. React DevTools can highlight prop changes; a changinglayoutIdwill cause a jump. - No conflicting CSS transforms – Ensure that any static
transformorfilterapplied via CSS or styled‑components does not interfere with the inline transform that Framer Motion injects. Use the browser inspector to confirm that the motion component receives astyleattribute with changingtranslate,scale, andopacityvalues. - Animation completes before unmount – The
AnimatePresencecomponent waits for the exit animation to finish before removing the element from the DOM. If you force an unmount (e.g., via a conditional that removes the wrapper prematurely), the layout animation will be cut off, causing a flash. Keep the wrapper present for the duration of the transition. - Performance budget – Open the Chrome Performance tab, record a navigation that triggers the shared‑element transition, and check that the main thread work per frame stays below ~16 ms (60 fps) on a representative mid‑tier device. Layout reads/writes are cheap for a single element but can add up on large lists.
Failure modes
- layoutId changes mid‑animation – If a rapid data update (e.g., a search filter) assigns a new ID to the same visual element while Framer Motion is still animating, the library aborts the transition and the element jumps to its new position.
- CSS transform conflicts – Applying a
transformvia a CSS class that changes during the animation adds to the motion‑generated transform, leading to unexpected scaling or drifting. - Excessive layout work on large lists – Animating dozens or hundreds of items simultaneously forces a layout read/write pass for each item, which can drop frames on lower‑end hardware.
When the design would change
If you need to animate more than just position and size (e.g., color, shadow, or custom SVG paths), you must supplement the layout animation with additional animate or variants props, or consider using useSpring from a physics‑based library. Likewise, if the shared element must persist across a full‑page reload (not just a client‑side route change), the layout‑ID approach alone is insufficient; you would need to serialize the ID in URL state or use a cross‑page persistence mechanism (e.g., sessionStorage) to re‑establish the identity after the reload.
In those scenarios, revisit the requirements: ensure the new animated properties are still layout‑safe (they do not trigger layout thrashing) and verify that the added animation logic does not break the stability checks above.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.