---
title: "How I use shared layout animations"
issue: 4
date: 2026-05-01
author: "Jakub Krehel"
canonical: https://interfaces.dev/magazine/issues/how-i-use-shared-layout-animations
access: free
---

# How I use shared layout animations

Shared layout animations in [Motion](https://motion.dev/docs/react-layout-animations) are *very* powerful. They let you smoothly animate between different elements across different states with a single line of code.

_[Interactive demo](https://interfaces.dev/magazine/issues/how-i-use-shared-layout-animations): Five tabs (Design, Engineering, Product, Marketing, Sales) rearrange three colored icon circles into rows and triangles with a spring while the arrow trails between them blur out and in._

## Single element

To use shared layout animations, first turn the element you want to animate into a `motion` component and add the `layoutId` prop.

_[Interactive demo](https://interfaces.dev/magazine/issues/how-i-use-shared-layout-animations): Five tabs move a single dark circle between the center and the four corners of the preview, each position being a separate element with the same layoutId._

In this example, a switch statement renders the same circle in different positions based on the selected tab.

```tsx title="shared-layout-tabs.tsx"
const getTabContent = () => {
    switch (activeTab) {
      case "Design":
        return (
            <motion.div
              className="size-16 rounded-full bg-gray-1200"
              layoutId="circle"
            />
        );
      case "Engineering":
        return (
          <motion.div
            className="absolute top-4 right-4 size-16 rounded-full bg-gray-1200"
            layoutId="circle"
          />
        );
      case "Product":
        return (
          <motion.div
            className="absolute top-4 left-4 size-16 rounded-full bg-gray-1200"
            layoutId="circle"
          />
        );
      case "Marketing":
        return (
          <motion.div
            className="absolute bottom-4 left-4 size-16 rounded-full bg-gray-1200"
            layoutId="circle"
          />
        );
      case "Sales":
        return (
          <motion.div
            className="absolute right-4 bottom-4 size-16 rounded-full bg-gray-1200"
            layoutId="circle"
          />
        );
      default:
        return null;
    }
  };
```

You can see that we don't manipulate an individual component. Instead, we render different components in different places in different states that happen to look the same. Their appearance can also be different but we'll cover that later.

All that is necessary for these elements to animate is that they share the same `layoutId` and Motion does all the heavy lifting to animate between them.

```tsx title="shared-layout-circle.tsx"
<motion.div
  className="size-16 rounded-full bg-gray-1200"
  layoutId="circle"
/>
```

The way this works under the hood is by using the [FLIP](https://css-tricks.com/animating-layouts-with-the-flip-technique/) technique, which stands for `First`, `Last`, `Invert`, `Play`.

If you're interested in how it works, I recommend reading [Inside Framer's Magic Motion](https://www.nan.fyi/magic-motion) by [Nanda](https://x.com/nandafyi), a beautiful article that explains the concept really well.

## Multiple elements

You can animate between multiple elements too.

_[Interactive demo](https://interfaces.dev/magazine/issues/how-i-use-shared-layout-animations): Five tabs rearrange orange, blue and green circles into different rows, a triangle and spread out corners, each circle animating to its new spot through its own layoutId._

As long as the elements share the same `layoutId` and that `id` is unique within the group, the animation will work correctly.

```tsx title="multiple-layout-ids.tsx"
<div className="relative flex h-full w-full items-center justify-center gap-8">
  <motion.div
    className="size-16 rounded-full bg-orange-500"
    layoutId="circle-1"
  />
  <motion.div
    className="size-16 rounded-full bg-blue-500"
    layoutId="circle-2"
  />
  <motion.div
    className="size-16 rounded-full bg-green-500"
    layoutId="circle-3"
  />
</div>
```

If you assign the same `layoutId` to multiple elements in the same state, the animation will break, like in the example below.

_[Interactive demo](https://interfaces.dev/magazine/issues/how-i-use-shared-layout-animations): The same three circle tabs labelled Wrong example, where the orange and blue circles are given one shared layoutId so their transitions between tabs break._

Now the only remaining thing is to animate the arrows between the circles to match the animation from the beginning of the article.

_[Interactive demo](https://interfaces.dev/magazine/issues/how-i-use-shared-layout-animations): The opening tabbed flow of three icon circles again, with the arrow trails between circles fading and blurring in and out as the circles animate between layouts._

The arrows are wrapped in `AnimatePresence` so they can fade in and out as the layout changes. The circles stay outside of `AnimatePresence`, since we don't want them to fade in and out.

```tsx title="arrow-presence.tsx"
<AnimatedCircle layoutId="circle-1" />

<AnimatePresence>
  <motion.div
    animate={{ opacity: 1, filter: "blur(0px)" }}
    exit={{ opacity: 0, filter: "blur(4px)" }}
    initial={{ opacity: 0, filter: "blur(4px)" }}
    key="divider-1"
  >
    <ArrowMarquee arrowCount={8} />
  </motion.div>
</AnimatePresence>

<AnimatedCircle layoutId="circle-2" />
```

Each arrow wrapper has a `key` that tells `AnimatePresence` to run the `exit` animation when the element unmounts and the `initial` animation when it mounts.

## Changing values between states

The elements you animate between can have different property values like `height`, `width`, `position` and more.

_[Interactive demo](https://interfaces.dev/magazine/issues/how-i-use-shared-layout-animations): Five tabs over a mock app layout where one dark card changes position, size and corner radius with a spring while the page content behind it fades, blurs and slides._

In this example, the elements have different positions, sizes and border radii. The border radius is passed to `animate` instead of a `className` so Motion can smoothly interpolate between the values.

```tsx title="card-layout.tsx"
const getCardConfig = () => {
  switch (activeTab) {
    case "Design":
      return {
        className: "top-24 left-46 h-12 w-32",
        borderRadius: "8px",
      };
    case "Engineering":
      return { className: "top-10 right-2 h-78 w-32", borderRadius: "8px" };
    case "Product":
      return { className: "bottom-2 left-2 h-20 w-28", borderRadius: "8px" };
    case "Marketing":
      return { className: "top-17 left-49 size-16", borderRadius: "4px" };
    case "Sales":
      return { className: "bottom-2 right-2 size-8", borderRadius: "32px" };
  }
};

<motion.div
  className={cn("absolute bg-gray-1200", cardConfig.className)}
  animate={{ borderRadius: cardConfig.borderRadius }}
  layoutId="card"
/>
```

`AnimatePresence` is also used to animate the content behind the moving element.

When combining `AnimatePresence` and shared layout animations, keep components with `layoutId` outside of `AnimatePresence` if you don't want the `initial` and `exit` animations to affect them during the layout transition.

If they are inside, the `initial` and `exit` animations will trigger while the elements are animating. This doesn't look great, especially if you are animating properties like `opacity`.

_[Interactive demo](https://interfaces.dev/magazine/issues/how-i-use-shared-layout-animations): The same mock layout with the card placed inside AnimatePresence and slowed to 1.2 seconds, making the card fade out and in with the content as it moves._

## Other examples

There are many other creative examples of shared layout animations. One of my personal favorites is a preview component like the one below. There are also other great examples in the [Motion](https://motion.dev/docs/react-layout-animations) documentation.

_[Interactive demo](https://interfaces.dev/magazine/issues/how-i-use-shared-layout-animations): An NFT card (Skull #1014, 0.145 ETH) that reveals expand, add to cart and Buy Now controls on hover, where expand morphs the image into a large dialog and back._

I used shared layout animations paired with the [Base UI Dialog primitive](https://base-ui.com/react/components/dialog). The idea is the same as in the other examples. There are two separate components: a card and a dialog. Motion animates between them using a shared `layoutId`.
