Shared layout animations in Framer Motion: layoutId, AnimatePresence, and frame budget
Use Framer Motion's layout and layoutId props to animate shared elements between states, with caveats for Strict Mode and transform conflicts.
11 Jul 2025, 07:16 UTC

The problem: content that jumps instead of moving
You have a settings panel that swaps between two views. When the user toggles, the new view appears and the old one disappears. It works, but it feels abrupt because the elements have no visual relationship between states. You could hand-calculate coordinates with keyframes, but that breaks as soon as padding, font size, or screen width changes.
The thesis: let Framer Motion measure the layout for you
Framer Motion’s layout prop makes a motion component animate automatically when its position or size changes. When two components share a layoutId, Framer Motion treats them as the same element across the tree and animates between their layouts. This is useful for shared element transitions, such as a list item expanding into a detail view or a tab panel moving between containers.
What layoutId actually matches
layoutId is a string identifier. When a component with a given layoutId unmounts and another component with the same ID mounts, Framer Motion attempts to cross-fade and translate between them. The components do not need to be siblings, but they must exist in the same Framer Motion tree during the transition.
Worked example: toggling a shared panel
Run this in your project’s terminal to add Framer Motion:
npm install framer-motion@latest react react-dom
No special permissions are required. Replace latest with a pinned version if your team locks dependencies, and check the Framer Motion changelog before upgrading because layout behavior has changed between major versions.
Here is a toggle that swaps between two panels. Both panels use the same layoutId, and AnimatePresence wraps them so the exiting panel can animate out instead of vanishing immediately.
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
export default function SharedPanel() {
const [showA, setShowA] = useState(true);
return (
setShowA(!showA)}>Toggle panel
{showA ? (
Panel A
) : (
Panel B with more padding
)}
);
}
Where to run: In a React component file in your frontend project.
Expected check: Clicking Toggle should cross-fade between panels while Panel B smoothly expands to fit its extra padding. Without layout, Panel B would snap into place.
Risk: If you omit AnimatePresence, the exiting panel unmounts immediately and the shared layout animation may not complete. Applying a CSS transform to a parent element can conflict with Framer Motion’s transform-based layout animation.
Trade-offs and limitations
- Strict Mode double mounting: In React 18 Strict Mode, components mount twice in development. Layout animations can flash or trigger unexpectedly during development even though production behaves normally. Test your production build, not just
npm run dev. - High-frequency updates: Do not use
layouton elements that change size during scroll, mouse move, or rapid typing. Framer Motion batches layout reads, but deep trees or constant re-layout can still consume frame budget. - Transform conflicts: Layout animations are driven by CSS transforms. Parent transforms, third-party carousel libraries, or CSS
scaleon the same element can fight Framer Motion’s matrix. - Exit animations:
layoutIdshared animations needAnimatePresencewithmode="popLayout"ormode="sync"to coordinate exiting and entering components.
How to verify the result
- Open Chrome DevTools and go to the Performance panel.
- Record a session while toggling the shared layout component.
- Check for long "Layout" or "Recalculate Style" tasks on every toggle. Occasional layout work is expected; repeated frames of layout work suggest the animation is triggering on every render.
- Open React DevTools Profiler and confirm the component does not re-render more than necessary when the layout changes.
- Test in a production build to avoid Strict Mode double-mount artifacts.
Closing
Use layout and layoutId when you need elements to move between states without manual coordinates. Pair them with AnimatePresence, avoid scroll-triggered layout changes, and verify in production builds. If your component tree is deep or updates constantly, measure before you commit to the pattern.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.