
Animate Layout Changes in Next.js Using Motion’s Layout Prop
- Author: Md. Saad
- Published at: July 20, 2025
- Updated at: July 21, 2025
Motion's layout prop is a beautifully simple way to animate position and size changes in your UI. Instead of handling element transitions manually, you simply declare which elements should animate when the layout changes, and Motion handles the rest. With the latest Motion, this is now faster and more modular, and works perfectly with modern frameworks like Next.js App Router.
In this guide, we're going to explore how Motion’s layout animation system works and how to implement it in a Next.js app using the layout, layoutId, and related props. Layout animations utilize performant transforms instead of direct CSS transitions, allowing even complex changes, such as reflows, resizes, and reorderings, to animate smoothly. These page transitions aren't just visually delightful; they also enhance the user experience by providing users with feedback on structural changes.
How It Works
The layout prop in <motion.div layout /> automatically tracks layout changes triggered by a React re-render and animates the element smoothly from its previous layout to the new one. This applies even to properties like justify-content, width, and grid layouts, elements that CSS transitions can't normally animate.
Getting Started: Setting Up Next.js with Motion
To begin, make sure you're using a fresh or existing Next.js App Router project. We install Motion by running:
npm install motion
Since animations require client-side interactivity, each component that uses Motion must be a Client Component; you’ll add "use client" at the top of each file. Then, instead of the older framer-motion import, you now import from motion/react:
import { motion, AnimatePresence } from "motion/react";
This ensures compatibility with server/client rendering in the App Router and gives you access to Motion's latest features.
Basic Usage of the Layout Prop
Let’s start with the simplest example, expanding and shrinking a box. In this case, we animate the width of a single div between 500px and 200px depending on a toggle state. This animation showcases the magic of the layout prop, which smoothly interpolates the layout difference without needing any manual transition configuration. By wrapping the component in a motion. div and adding the layout prop, Motion watches for changes triggered by a React re-render and animates the transition using performant transforms.
Component: BasicBox.jsx
"use client";
import {useState} from "react";
import {motion} from "motion/react";
export default function BasicBox() {
const [open, setOpen] = useState(false);
return (
<motion.div
layout
onClick={() => setOpen(!open)}
className={`mx-auto my-8 cursor-pointer rounded-xl bg-indigo-600 h-28 ${open ? "w-[500px]" : "w-[200px]"}`}
transition={{layout: {duration: 0.45}}}
/>
);
}
Flex Alignment Animation: Switching Justify-Content
let’s explore an animation that CSS can’t normally handle: flex alignment. Here we create a flex container that toggles justify-content between flex-start and flex-end. Normally, this change would instantly snap the child items across the container. But with Motion’s layout prop, these items glide smoothly into place. The entire transition is automatic; just set the justifyContent property based on state and let Motion do the rest.
Component: FlexToggle.jsx
"use client";
import {useState} from "react";
import {motion} from "motion/react";
export default function FlexToggle() {
const [left, setLeft] = useState(true);
return (
<motion.div
layout
onClick={() => setLeft(!left)}
className={`mx-auto my-8 flex w-[470px] gap-3 rounded-lg bg-slate-200 p-6 ${
left ? "justify-start" : "justify-end"
}`}
>
<motion.div
layout
className="h-10 w-10 rounded bg-sky-500"
/>
<motion.div
layout
className="h-10 w-10 rounded bg-sky-500"
/>
<motion.div
layout
className="h-10 w-10 rounded bg-sky-500"
/>
</motion.div>
);
}
Shared Layout Animation: Button Morphing Into Modal
One of the most visually impressive layout animations is the shared layout transition using layoutId. In this example, clicking a button opens a modal that morphs out of the button itself. Both the button and the modal share the same layoutId, allowing Motion to animate between their layout states. With AnimatePresence, the modal can smoothly animate out before being removed from the DOM. This creates an elegant, seamless experience that would normally require extensive choreography.
Component: SharedModal.jsx
"use client";
import {useState} from "react";
import {motion, AnimatePresence} from "motion/react";
export default function SharedModal() {
const [open, setOpen] = useState(false);
return (
<>
<motion.button
layoutId="modal"
onClick={() => setOpen(true)}
transition={{type: "spring", stiffness: 300, damping: 25}}
className="rounded-xl bg-emerald-500 px-6 py-2 text-white shadow-md"
>
Open
</motion.button>
<AnimatePresence>
{open && (
<motion.div
layoutId="modal"
onClick={() => setOpen(false)}
transition={{duration: 0.3}}
className="fixed inset-0 m-auto flex h-56 w-80 items-center justify-center rounded-xl bg-white shadow-2xl text-black"
>
Click to close
</motion.div>
)}
</AnimatePresence>
</>
);
}
Scrollable Container Animation: List Items Inside Scroll View
When layout changes happen inside a scrollable container, Motion needs help understanding each child’s position relative to scroll. This is where layoutScroll comes in. In the following example, we display a scrollable list of blocks that reshuffle order when the "Shuffle" button is clicked. By adding the layoutScroll prop to the parent container, Motion accounts for scroll position and animates the movement of each item correctly.
Component: ScrollableList.jsx
"use client";
import {useState} from "react";
import {motion} from "motion/react";
const colors = ["bg-orange-500", "bg-orange-400", "bg-yellow-300", "bg-emerald-400", "bg-blue-400", "bg-violet-400"];
export default function ScrollableList() {
const [items, setItems] = useState([...Array(12).keys()]);
const shuffle = () => setItems((prev) => [...prev].sort(() => Math.random() - 0.5));
return (
<div className="mx-auto my-8 w-80">
<button
onClick={shuffle}
className="mb-3 rounded bg-slate-300 px-3 py-1"
>
Shuffle
</button>
<motion.div
layoutScroll
className="max-h-52 overflow-y-auto rounded border border-slate-300 p-3"
>
{items.map((n) => (
<motion.div
key={n}
layout
className={`mb-2 h-10 rounded ${colors[n % colors.length]}`}
/>
))}
</motion.div>
</div>
);
}
Fixed Container Layout Animation: Shrinking Header on Scroll
Another situation that benefits from layout-aware animation is fixed-position elements. Motion can animate headers or toolbars that are fixed to the top of the page, but it needs the layoutRoot prop to measure layout changes correctly. In the next example, we animate a fixed header that shrinks in height and font size as the user scrolls. The combination of layoutRoot on the container and layout on the header element ensures a smooth transition as we track scroll position with a useEffect.
Component: FixedHeader.jsx
"use client";
import {useEffect, useState} from "react";
import {motion} from "motion/react";
export default function FixedHeader() {
const [small, setSmall] = useState(false);
useEffect(() => {
const onScroll = () => setSmall(window.scrollY > 120);
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, []);
return (
<motion.header
layoutRoot
className="fixed inset-x-0 top-0 z-30"
>
<motion.div
layout
transition={{duration: 0.3}}
className={`flex w-full items-center justify-center bg-slate-800 text-white ${
small ? "h-12 text-base" : "h-24 text-xl"
}`}
>
Motion Header
</motion.div>
</motion.header>
);
}
Grouped Layout Animation: Expanding Accordions Together
Often, two components affect each other’s layout but don’t re-render together. This can cause janky UI changes unless you use a LayoutGroup. Let’s have an example that demonstrates grouped layout animations. In this accordion list example, we wrap two expanding/collapsing items in a LayoutGroup. Even though only one is open at a time, both animate their layout changes smoothly and in sync, giving a cohesive experience.
Component: AccordionList.jsx
"use client";
import {useState} from "react";
import {motion, LayoutGroup} from "motion/react";
function Accordion({title, children}) {
const [open, setOpen] = useState(false);
return (
<motion.div
layout
onClick={() => setOpen(!open)}
className="mb-3 cursor-pointer rounded-lg bg-slate-100 p-4"
>
<h4 className="m-0 font-medium text-black">{title}</h4>
{open && (
<motion.p
layout
initial={{opacity: 0}}
animate={{opacity: 1}}
className="mt-2 text-slate-700"
>
{children}
</motion.p>
)}
</motion.div>
);
}
export default function AccordionList() {
return (
<LayoutGroup>
<Accordion title="What is Motion?">A lightning‑fast animation library for React.</Accordion>
<Accordion title="Why layout animations?">They make complex state changes feel effortless.</Accordion>
</LayoutGroup>
);
}
Animating Tabs with Shared Layout and Crossfade Panels
A tab switcher is another everyday pattern that benefits from Motion’s layout system. When a user clicks between tabs we want two things to animate: an underline gliding to the active label and the panel itself fading (or sliding) into view. Both motions make the change of state obvious, preserving context as content swaps. Motion makes this easy by sharing a layoutId between successive underline elements, while AnimatePresence handles smooth mounting and unmounting of the tab panels. Tailwind CSS keeps the markup terse, flex for the tab row, relative for positioning, and utility classes for spacing, colors and typography. Notice that the underline is simply a motion.div absolutely positioned at the bottom of the active tab; because each underline instance carries the same layoutId="underline", Motion morphs it from the old tab’s bounds to the new one. The panels themselves reside in a keyed map, so React re-renders only the active panel, and initial/animate/exit let us cross-fade their opacity.
Component: TabSwitcher.jsx
"use client";
import {useState} from "react";
import {motion, AnimatePresence} from "motion/react";
const tabs = [
{id: "first", label: "First Tab", content: "Here is the first panel."},
{id: "second", label: "Second Tab", content: "Here is the second panel."},
{id: "third", label: "Third Tab", content: "And finally the third."},
];
export default function TabSwitcher() {
const [active, setActive] = useState("first");
return (
<div className="mx-auto my-8 w-full max-w-xl">
{/* tab bar */}
<div className="relative flex border-b border-slate-300">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActive(tab.id)}
className={`relative px-6 py-3 text-sm font-medium transition-colors ${
active === tab.id ? "text-indigo-600" : "text-slate-600"
}`}
>
{tab.label}
{active === tab.id && (
<motion.div
layoutId="underline"
className="absolute bottom-0 left-0 h-[2px] w-full rounded bg-indigo-600"
/>
)}
</button>
))}
</div>
{/* panel area */}
<div className="mt-6 min-h-[6rem]">
<AnimatePresence mode="wait">
{tabs
.filter((t) => t.id === active)
.map((t) => (
<motion.p
key={t.id}
initial={{opacity: 0, y: 8}}
animate={{opacity: 1, y: 0}}
exit={{opacity: 0, y: -8}}
transition={{duration: 0.25}}
className="text-slate-700"
>
{t.content}
</motion.p>
))}
</AnimatePresence>
</div>
</div>
);
}
Conclusion
Motion’s layout animations turn traditionally rigid layout shifts into smooth, engaging transitions that feel natural. Whether you're building a dynamic dashboard, animating modals, or syncing multiple components, the layout and layoutId props offer one of the most developer-friendly ways to breathe life into your React UIs, especially in Next.js.
To make your website smooth and visually appealing, enhancing the user experience, contact us. We will guide you on the journey.
FAQs
The layout prop in Framer Motion enables automatic animation of layout changes, such as size, position, or alignment, within a React component. When used in Next.js, it smoothly transitions elements on re-render without manual animation configuration. This makes UI interactions feel polished and dynamic, particularly for resizes, flex changes, and element reordering.
layoutId is a prop that connects two elements in different parts of the DOM to enable seamless shared layout transitions. In Next.js, it’s commonly used for animations like morphing buttons into modals or switching tabs. By assigning the same layoutId, Framer Motion animates from the source to the destination element, even if they’re rendered at different times.
Yes, Framer Motion is fully compatible with the Next.js App Router when imported from motion/react. Each animated component should be marked as a client component. For layout-based animations, use props like layout, layoutId, layoutScroll, and layoutRoot to ensure smooth transitions and full support within modern React server-client rendering.
To animate elements inside a scrollable container, use the layoutScroll prop on the container. This tells Framer Motion to account for the scroll position during layout animations. It’s particularly useful for lists or grids where items move or reshuffle, ensuring transitions look smooth even while the user scrolls.
Layout animations provide visual continuity when a component's structure or position changes. Instead of sudden UI jumps, animations guide the user’s attention and reduce cognitive load. In modern web apps, they help users understand relationships between elements, improving usability and perceived performance, especially important in React and Next.js interfaces.
- layout tracks and animates changes to an individual component’s size or position.
- layoutId enables shared layout animations between two components that appear at different times.
- LayoutGroup synchronizes layout animations across multiple independent components to maintain layout integrity.
Each serves a distinct role, but they can be combined to deliver highly interactive and cohesive animations in a Next.js project.