EmpireUI
Get Pro
← Blog8 min read#framer motion#react#layout animation

Framer Motion Advanced: Layout Animations, Shared Elements, useAnimate

Master Framer Motion's layout animations, shared element transitions, and the useAnimate hook to build fluid, production-ready React UIs in 2026.

Abstract fluid motion animation with glowing color transitions on dark background

Why Framer Motion's Advanced APIs Are Worth Your Time

Most React devs hit Framer Motion at the animate prop and stop there. That's fine for fades and slides, but you're leaving the best stuff untouched. Layout animations, shared element transitions, and useAnimate are what separate polished production UIs from the toy demos you see on Twitter.

Framer Motion hit v11 in 2024 and the ergonomics genuinely improved. The useAnimate hook in particular replaced a lot of clunky imperative animation patterns that previously required reaching for refs and orchestrating timelines by hand. Now you get a clean, composable API that plays nicely with React's render cycle.

Honestly, if you've been hand-rolling CSS transitions for layout changes, you've been doing it the hard way. One layout prop on a motion.div and Framer handles the FLIP math for you — position, size, the whole thing. Worth knowing before you write another transition: all 0.3s ease.

That said, these APIs have quirks. layoutId mismatches will silently do nothing. Shared element transitions break if your components unmount in the wrong order. This article walks through what actually works, with code you can drop into a real project.

Layout Animations: The layout Prop Explained

The layout prop is deceptively simple. You add it to a motion element and Framer automatically animates any positional or size change between renders. No keyframes. No calculated deltas. It uses the FLIP technique — First, Last, Invert, Play — which records the element's position before and after a DOM change, then animates the inversion.

Here's the most common pattern: a list that reorders when you click an item. Without layout, items snap. With it, they glide.

import { motion, AnimatePresence } from 'framer-motion';

function SortableList({ items }) {
  return (
    <ul style={{ listStyle: 'none', padding: 0 }}>
      {items.map((item) => (
        <motion.li
          key={item.id}
          layout
          transition={{ type: 'spring', stiffness: 350, damping: 30 }}
          style={{
            padding: '12px 16px',
            marginBottom: 8,
            borderRadius: 8,
            background: '#1a1a2e',
          }}
        >
          {item.label}
        </motion.li>
      ))}
    </ul>
  );
}

Quick aside: the transition you pass to a layout-animated element applies specifically to the layout animation, not to animate prop changes. They're separate. A lot of people miss this and wonder why their spring settings aren't doing anything.

You can also scope layout animations with LayoutGroup. Wrap sibling components that need to coordinate — say a tabbed nav where the active indicator slides between tabs — and Framer knows to batch those FLIP calculations together. Without LayoutGroup, elements in separate React subtrees can't talk to each other for layout purposes.

Shared Element Transitions with layoutId

layoutId is the feature that makes people say "how did you do that?" You assign the same string ID to two different motion elements — one in a list, one in a modal or expanded view — and Framer morphs between them when one mounts and the other unmounts. It looks like native iOS transitions. In a web app. Built in five minutes.

The mechanics: when an element with a given layoutId unmounts and another with the same ID mounts, Framer intercepts the animation and plays a shared transition between the two positions and sizes. The key requirement is that only one element with a given layoutId should be in the DOM at a time, or you'll get weird ghosting.

import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';

const cards = [
  { id: 'card-1', title: 'Aurora UI', color: '#6366f1' },
  { id: 'card-2', title: 'Glassmorphism', color: '#06b6d4' },
];

export function CardGrid() {
  const [selected, setSelected] = useState(null);

  return (
    <>
      <div style={{ display: 'flex', gap: 16 }}>
        {cards.map((card) => (
          <motion.div
            key={card.id}
            layoutId={card.id}
            onClick={() => setSelected(card)}
            style={{
              width: 120,
              height: 80,
              borderRadius: 12,
              background: card.color,
              cursor: 'pointer',
            }}
          />
        ))}
      </div>

      <AnimatePresence>
        {selected && (
          <motion.div
            layoutId={selected.id}
            onClick={() => setSelected(null)}
            style={{
              position: 'fixed',
              inset: 0,
              margin: 'auto',
              width: 320,
              height: 200,
              borderRadius: 20,
              background: selected.color,
              zIndex: 100,
            }}
          />
        )}
      </AnimatePresence>
    </>
  );
}

In practice, you'll want to wrap the expanded view in AnimatePresence so Framer can handle exit animations correctly. Without it, the component unmounts immediately and there's nothing to transition back to. The mode="wait" option on AnimatePresence helps if you're switching between multiple shared elements quickly.

Worth noting: if the two elements have significantly different border-radius or padding values — say the card has border-radius: 12px and the modal has border-radius: 24px — those properties animate too. You get a fully interpolated morph, not just a position/size tween. That's 24px versus 12px in this case, and the difference is visible.

useAnimate: Imperative Animations Without the Pain

Sometimes you need to trigger an animation in response to an event, not a state change. useAnimate gives you a scope ref and an animate function you can call directly. Think confirmation flashes, error shakes, multi-step sequences with async/await.

The API is genuinely clean. You get [scope, animate] from the hook, attach scope to a container element, then use CSS selector strings inside animate to target children. No hunting through refs. No useRef on every animated child.

import { useAnimate } from 'framer-motion';

export function SubmitButton({ onSubmit }) {
  const [scope, animate] = useAnimate();

  async function handleClick() {
    // Shake on error
    await animate(scope.current, {
      x: [0, -8, 8, -8, 8, 0],
    }, { duration: 0.4 });

    const success = await onSubmit();

    if (success) {
      // Scale + fade children on success
      await animate('span', { opacity: [1, 0], scale: [1, 0.8] }, { duration: 0.2 });
      await animate(scope.current, { background: '#22c55e' }, { duration: 0.3 });
    }
  }

  return (
    <button ref={scope} onClick={handleClick} style={{ padding: '12px 24px', borderRadius: 8 }}>
      <span>Submit</span>
    </button>
  );
}

Look, useAnimate also returns cancel and complete utilities that let you abort running animations. This matters for cleanup on unmount — something useEffect returning cleanup logic used to handle awkwardly with older Framer APIs. Now it's one line.

If you're building interactive UI components and want to see how these patterns combine with design systems, browse the components on Empire UI. There's good reference material for motion-heavy design styles like aurora and cyberpunk that use layered animation patterns.

Combining Layout and Presence for Real UI Patterns

The real magic happens when you stack these primitives. A list where items animate on add/remove (AnimatePresence), reorder smoothly (layout), and expand into a detail view via shared transitions (layoutId) — that's three APIs working together, and it's maybe 40 extra lines of JSX.

One pattern that trips people up: AnimatePresence needs to be the direct parent of the component you're animating in and out. If you stick another wrapper div in between, the exit animation won't fire. The component has to be a direct child so Framer can intercept the unmount.

Also worth knowing — layout and layoutId don't play well with CSS transform on parent elements. If you've got a container with transform: translateZ(0) for GPU acceleration (a 2023-era perf hack you're probably still carrying around), it creates a new stacking context that can break FLIP calculations. Pull that will-change: transform off the parent if your layout animations look wrong.

For glassmorphism-style UIs where cards blur and float, check out the glassmorphism components on Empire UI. The translucency means you can see through elements during shared transitions, which looks incredible when it's done right — but requires careful z-index management so the blurred background doesn't flicker mid-animation.

Performance Gotchas You Need to Know

Layout animations recalculate layout on every frame during the transition. For a list of 5 items that's nothing. For a list of 200 items with complex children, you'll feel it. Framer does batch reads and writes to avoid layout thrashing, but there's a ceiling. Keep animated lists short or virtualize them.

The transform and opacity properties animate on the compositor thread — no layout recalc, silky smooth. But width, height, padding, border-radius all trigger layout. The layout prop handles this via FLIP (animating transform under the hood), which is why it's fast even though it looks like it's animating dimensions. Don't animate width directly unless you have no choice.

One more thing — if you're seeing dropped frames in Chrome DevTools during layout animations, check for overflow: hidden on a parent element. It forces a paint boundary that can slow down FLIP on some GPU configurations. Setting overflow: clip instead (supported since Chrome 90) avoids the stacking context issue while still clipping content.

Putting It Together: When to Use Which API

Here's the cheat sheet: use layout when DOM position or size changes due to a React state update. Use layoutId when you're visually connecting two logically related elements across a mount/unmount boundary. Use useAnimate when you need to trigger animations imperatively — event handlers, async flows, sequences.

You don't have to choose just one. A card grid could use layout for reordering, layoutId for the expand-to-modal transition, and useAnimate inside the modal for a staggered entrance of content elements. That's real-world usage, and it works exactly as you'd expect.

If you're building these interactions into a full design system, the templates on Empire UI give you a head start — several of them already use Framer Motion for transitions and you can see how the layout patterns integrate with component architecture. And if you want to build visually dense animated UIs, the glassmorphism generator lets you prototype the visual layer before you wire up the motion.

FAQ

What's the difference between layout and layoutId in Framer Motion?

layout animates a single element when its position or size changes within the DOM. layoutId connects two different elements — typically across a mount/unmount — so Framer can morph between them as a shared transition.

Does useAnimate work with server components in Next.js?

No — useAnimate is a React hook and requires a client component. Add 'use client' at the top of any file using it, or wrap your animated component in a client boundary.

Why is my layoutId shared transition not animating?

Most likely two elements with the same layoutId are in the DOM simultaneously, or the exiting element isn't wrapped in AnimatePresence. Both conditions silently prevent the transition from firing.

Can I use Framer Motion layout animations with CSS Grid or Flexbox reordering?

Yes, and it works well. Add the layout prop to each motion child inside a flex or grid container. Framer measures positions before and after the reorder and animates the delta via transform.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Framer Motion Layout Animations: shared layout, AnimatePresenceMotion for React (Framer Motion) in 2026: layout, AnimatePresence, GesturesFramer Motion in React: Animations That Feel AliveNeon Text Effect in React: Animated Glow with CSS and Framer Motion