Edit Page

#AnimateSharedLayout

Animate layout changes across, and between, multiple components.

The AnimateSharedLayout component enables you to perform layout animations:

  • Across a set of components that don't otherwise share state.
  • Between different components that share a layoutId as they're added/removed.
import { AnimateSharedLayout } from "framer-motion"

#Syncing layout animations

motion components with a layout prop will automatically animate layout changes that occur when they re-render.

<motion.div layout />

However, if a layout component changes layout due to local state change, surrounding components need a way to know they should animate their layout, too.

function Item({ content }) {
  const [isOpen, setIsOpen] = useState(false)

  return <motion.div layout>{isOpen && content}</motion.div>
}

function List({ items }) {
  /**
   * This wrapping motion.ul, and sibling
   * Item components won't animate layout
   * when an Item opens/closes
   */
  return (
    <motion.ul layout>
      {items.map(item => (
        <Item content={item.content} />
      ))}
    </motion.ul>
  )
}

By wrapping components with AnimateSharedLayout, all layout components within will inform each other when their layout changes, and animate accordingly.

Open sandbox
function List({ items, selectedId }) {
  return (
    <AnimateSharedLayout>
      <motion.ul layout>
        {items.map(item => (
          <Item content={item.content} />
        ))}
      </motion.ul>
    </AnimateSharedLayout>
  )
}

#Animate between components

AnimateSharedLayout can be used to animate between completely different motion components that share the same layoutId prop.

In this example, each item contains a motion component with a layoutId="outline" prop that gets rendered only if the item is selected.

Open sandbox
isSelected && <motion.div layoutId="underline" />

When a new component with a layoutId gets added as another gets removed, the component will perform a layout animation from previous component.

The new component will also inherit any animating values from the old component as its initial state. So visually it'll be treated as one continuous component.

<motion.div
  layoutId="underline"
  initial={false}
  animate={{ backgroundColor: "#ff0000" }}
/>

Note: If the previous component remains in the tree when the new one is added, it'll automatically be hidden using visibility: hidden. If the new component is subsequently removed, the previous component will be set back to visible.

#Experimental

The following features are considered experimental. Their API is stable, but due to the number of implementation permutations it's possible we haven't caught every bug or addressed every use-case. If you run into bugs or unexpected behaviours, please open an issue.

#Shared drag session

A motion component can be provided both a drag and a layoutId prop.

If a component is being dragged when it's removed, and a new component with the same layoutId is added elsewhere within the same AnimateSharedLayout component, the drag gesture will continue on the new component.

Open sandbox
<motion.div drag layoutId="box" />

In a future release, the component will perform a layout animation to its new size.

#AnimatePresence

If a component with a layoutId is added, and the existing component with that same layoutId is still rendered, the older component will automatically be hidden. The new component will animate out from its position.

By wrapping the new component in AnimatePresence, when it's removed it'll automatically animate back to the original component's position as an exit animation.

<AnimateSharedLayout>
  {images.map((img) => <motion.img layoutId={img.id} />)}
  <AnimatePresence>
    {selectedId && <motion.img layoutId={selectedId} />)}
  </AnimatePresence>
</AnimateSharedLayout>

#Crossfade

When animating between two components using AnimatePresence, they might look quite different to each other.

For instance, they might be images with a different aspect ratio, or a text box with different text wrapping.

Switching instantly between the two components at the start and end of the animations might look odd.

By adding type="crossfade" to AnimateSharedLayout, immediate children of AnimatePresence will crossfade with their old component, smoothing this transition.

Open sandbox
<AnimateSharedLayout type="crossfade">
  {images.map((img) => <motion.img layoutId={img.id} />)}
  <AnimatePresence>
    {selectedId && <motion.img layoutId={selectedId} />)}
  </AnimatePresence>
</AnimateSharedLayout>

#Props

#type: "switch" | "crossfade"

When combined with AnimatePresence, SharedLayoutProps can customise how to visually switch between layoutId components as new ones enter and leave the tree.

  • "switch" (default): The old layoutId component will be hidden instantly when a new one enters, and the new one will perform the full transition. When the newest one is removed, it will perform the full exit transition and then the old component will be shown instantly. "crossfade": The root shared components will crossfade as layoutId children of both perform the same transition.