Documentation

Documentation

React

Layout animations

Layout animations

Motion's industry-leading layout animations makes it easy to animate between any two layouts, even between different elements.

It's as simple as a layout prop.

<motion.div layout />

One of the coolest things about layout animations is that they effectively make any CSS property transitionable. You can’t apply a CSS transition to flex-direction or grid-template-columns, but with layout animations, you can. Plus, the animation uses super-efficient transforms, so the motion will be smooth!

~ Josh W. Comeau, The Joy of React

This little prop can animate previously unanimatable CSS values, like switching justify-content between flex-start and flex-end.

<motion.div
  layout
  style={{ justifyContent: isOn ? "flex-start" : "flex-end" }}
/>

Or by using the layoutId prop, it's possible to match two elements and animate between them for some truly advanced animations.

<motion.li layoutId="item" />

Usage

Any layout change that happens as a result of a React re-render can be animated.

<motion.div layout style={{ width: isOpen ? "80vw" : 0 }} />

Note that CSS changes should happen immediately via style, not animate, as layout will take care of the animation.

Layout changes can be anything, changing width/height, number of grid columns, reordering a list, or adding/removing new items:

Shared layout animations

When a new component is added that has a layoutId prop that matches an existing component, it will automatically animate out from the old component.

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

If the old component is still mounted when the new component enters, they will automatically crossfade from the old to the new.

When removing an element to animate back to its origin layout, AnimatePresence can be used to keep it in the DOM until its exit animation has finished.

<AnimatePresence>
  {isOpen && <motion.div layoutId="modal" />}
</AnimatePresence>

Setting a transition

Layout animations can be customised using the transition prop.

<motion.div layout transition={{ duration: 0.3 }} />

If you want to set a transition specifically for only the layout animation, you can specify a specific layout transition.

<motion.div
  layout
  animate={{ opacity: 0.5 }}
  transition={{
    default: { ease: "linear" },
    layout: { duration: 0.3 }
  }}
/>

When performing a shared layout animation, the transition defined for element we're animating to will be used.

<>
  <motion.button
    layoutId="modal"
    onClick={() => setIsOpen(true)}
    // This transition will be used when the modal closes
    transition={{ type: "spring" }}
  >
    Open
  </motion.button>
  <AnimatePresence>
    {isOn && (
      <motion.dialog
        layoutId="modal"
        // This transition will be used when the modal opens
        transition={{ duration: 0.3 }}
      />
    )}
  </AnimatePresence>
</>

Animating within scrollable element

To correctly animate layout within scrollable elements, we need to provide them the layoutScroll prop.

<motion.div layoutScroll style={{ overflow: "scroll" }} />

This lets Motion account for the element's scroll offset when measuring children.

Animating within fixed containers

To correctly animate layout within fixed elements, we need to provide them the layoutRoot prop.

<motion.div layoutRoot style={{ position: "fixed" }} />

This lets Motion account for the page's scroll offset when measuring children.

Group layout animations

Layout animations are triggered when a component re-renders and its layout has changed.

function Accordion() {
  const [isOpen, setOpen] = useState(false)
  
  return (
    <motion.div
      layout
      style={{ height: isOpen ? "100px" : "500px" }}
      onClick={() => setOpen(!isOpen)}
    />
  )
}

But what happens when we have two or more components that don't re-render at the same time, but do affect each other's layout?

function List() {
  return (
    <>
      <Accordion />
      <Accordion />
    </>  
  )
}

When one re-renders, for performance reasons the other won't be able to detect changes to its layout.

We can synchronise layout changes across multiple components by wrapping them in the LayoutGroup component.

import { LayoutGroup } from "motion/react"

function List() {
  return (
    <LayoutGroup>
      <Accordion />
      <Accordion />
    </LayoutGroup>  
  )
}

When layout changes are detected in any grouped motion component, layout animations will trigger across all of them.

Scale correction

All layout animations are performed using the transform style, resulting in smooth framerates.

Animating layout using transforms can sometimes visually distort children. To correct this distortion, the first child elements of the element can also be given layout property.

Open this sandbox and try removing layout from the pink dot to see the difference this will make:

Transforms can also distort boxShadow and borderRadius. The motion component will automatically correct this distortion on both props, as long as they're set as motion values.

If you're not animating these values, the easiest way to do this is to set them via style.

<motion.div layout style={{ borderRadius: 20 }} />

Troubleshooting

The component isn't animating

Ensure the component is not set to display: inline, as browsers don't apply transform to these elements.

Ensure the component is re-rendering when you expect the layout animation to start.

SVG layout animations are broken

SVG components aren't currently supported with layout animations. SVGs don't have layout systems so it's recommended to directly animate their attributes like cx etc.

The content stretches undesirably

This is a natural side-effect of animating width and height with scale.

Often, this can be fixed by providing these elements a layout animation and they'll be scale-corrected.

<motion.section layout>
  <motion.img layout />
</motion.section>

Some elements, like images or text that are changing between different aspect ratios, might be better animated with layout="position".

Border radius or box shadows are behaving strangely

Animating scale is performant but can distort some styles like border-radius and box-shadow.

Motion automatically corrects for scale distortion on these properties, but they must be set on the element via style.

<motion.div layout style={{ borderRadius: 20 }} />

Border looks stretched during animation

Elements with a border may look stretched during the animation. This is for two reasons:

  1. Because changing border triggers layout recalculations, it defeats the performance benefits of animating via transform. You might as well animate width and height classically.

  2. border can't render smaller than 1px, which limits the degree of scale correction that Motion can perform on this style.

A work around is to replace border with a parent element with padding that acts as a border.

<motion.div layout style={{ borderRadius: 10, padding: 5 }}>
  <motion.div layout style={{ borderRadius: 5 }} />
</motion.div>

Technical reading

Interested in the technical details behind layout animations? Nanda does an incredible job of explaining the challenges of animating layout with transforms using interactive examples. Matt, creator of Motion, did a talk at Vercel conference about the implementation details that is largely up to date.

Differences with the View Transitions API

More browsers are starting to support the View Transitions API, which is similar to Motion's layout animations.

Benefits of View Transitions API

The main two benefits of View Transitions is that it's included in browsers and features a unique rendering system.

Filesize

Because the View Transitions API is already included in browsers, it's cheap to implement very simple crossfade animations.

However, the CSS complexity can scale quite quickly. Motion's layout animations are around 12kb but from there it's very cheap to change transitions, add springs, mark matching

Rendering

Whereas Motion animates the elements as they exist on the page, View Transitions API does something quite unique in that it takes an image snapshot of the previous page state, and crossfades it with a live view of the new page state.

For shared elements, it does the same thing, taking little image snapshots and then crossfading those with a live view of the element's new state.

This can be leveraged to create interesting effects like full-screen wipes that aren't really in the scope of layout animations. Framer's Page Effects were built with the View Transitions API and it also extensively uses layout animations. The right tool for the right job.

Drawbacks to View Transitions API

There are quite a few drawbacks to the API vs layout animations:

  • Not interruptible: Interrupting an animation mid-way will snap the animation to the end before starting the next one. This feels very janky.

  • Blocks interaction: The animating elements overlay the "real" page underneath and block pointer events. Makes things feel quite sticky.

  • Difficult to manage IDs: Layout animations allow more than one element with a layoutId whereas View Transitions will break if the previous element isn't removed.

  • Less performant: View Transitions take an actual screenshot and animate via width/height vs layout animation's transform. This is measurably less performant when animating many elements.

  • Doesn't account for scroll: If the page scroll changes during a view transition, elements will incorrectly animate this delta.

  • No relative animations: If a nested element has a delay it will get "left behind" when its parent animates away, whereas Motion handles this kind of relative animation.

  • One animation at a time: View Transitions animate the whole screen, which means combining it with other animations is difficult and other view animations impossible.

All-in-all, each system offers something different and each might be a better fit for your needs. In the future it might be that Motion also offers an API based on View Transitions API.

A Motion+ membership will give you early access to features & content, access to our private Github and Discord, and more.

It's a one-time fee that grants lifetime access. No subscription necessary!

MOTION+

Introducing Cursor

Cursor is a creative cursor component for React, that makes it super easy to make custom cursor and follow cursor effects.


Hover over these examples to check out some of what it can do:

Custom cursor

Follow with spring

Multicursor

?

?