Documentation

Documentation

Layout animation

Motion makes it simple to animate an element's size and position between different layouts. With the layout prop, you can perform layout animations, and by using layoutId you can create seamless "magic motion" effects between two separate elements.

In this guide, we'll learn how to:

  • Animate layout changes with a single prop.

  • Create shared element transitions between components.

  • Explore advanced techniques.

  • Troubleshoot common layout animation issues.

  • Understand the differences between Motion and the native View Transitions API.

How to animate layout changes

To enable layout animations on a motion component, simply add the layout prop. Any layout change that happens as a result of a React render will now be automatically animated.

<motion.div layout />

Layout animation 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" />

It can handle anything from microinteractions to full page transitions.

Note: Layout changes should happen statically via style or className, not via animation props like animate or whileHover, 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:

Performance

Animating layout is traditionally slow, but Motion performs all layout animations using the CSS transform property for the highest possible performance.

Shared layout animations

For more advanced shared layout animations, layoutId allows you to connect two different elements.

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

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

If the original component is still on the page when the new one enters, they will automatically crossfade.

To animate an element back to its origin, you can use the AnimatePresence component to keep it in the DOM until its exit animation has finished.

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

Customise a layout animation

Layout animations can be customised using the transition prop.

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

If you need to set a transition specifically for the layout animation while having a different transition for other properties (like opacity), you can define a dedicated layout transition.

<motion.div
  layout
  animate={{ opacity: 0.5 }}
  transition={{
    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>
</>

Advanced use-cases

Animating within scrollable element

To correctly animate layout within a scrollable container, you must add the layoutScroll prop to the scrollable element. This allows Motion to account for the element's scroll offset.

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

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

Because layout animations use transform: scale(), they can sometimes visually distort children or certain CSS properties.

  • Child elements: To fix distortion on direct children, these can also be given the layout prop.

  • Border radius and box shadow: Motion automatically corrects distortion on these properties, but they must be set via the style, animate or other animation prop.

<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.

Animations don't work during window resize

Layout animations are blocked during horizontal window resize to improve performance and to prevent unnecessary animations.

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.

Motion's layout animations vs 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.

Motion makes it simple to animate an element's size and position between different layouts. With the layout prop, you can perform layout animations, and by using layoutId you can create seamless "magic motion" effects between two separate elements.

In this guide, we'll learn how to:

  • Animate layout changes with a single prop.

  • Create shared element transitions between components.

  • Explore advanced techniques.

  • Troubleshoot common layout animation issues.

  • Understand the differences between Motion and the native View Transitions API.

How to animate layout changes

To enable layout animations on a motion component, simply add the layout prop. Any layout change that happens as a result of a React render will now be automatically animated.

<motion.div layout />

Layout animation 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" />

It can handle anything from microinteractions to full page transitions.

Note: Layout changes should happen statically via style or className, not via animation props like animate or whileHover, 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:

Performance

Animating layout is traditionally slow, but Motion performs all layout animations using the CSS transform property for the highest possible performance.

Shared layout animations

For more advanced shared layout animations, layoutId allows you to connect two different elements.

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

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

If the original component is still on the page when the new one enters, they will automatically crossfade.

To animate an element back to its origin, you can use the AnimatePresence component to keep it in the DOM until its exit animation has finished.

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

Customise a layout animation

Layout animations can be customised using the transition prop.

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

If you need to set a transition specifically for the layout animation while having a different transition for other properties (like opacity), you can define a dedicated layout transition.

<motion.div
  layout
  animate={{ opacity: 0.5 }}
  transition={{
    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>
</>

Advanced use-cases

Animating within scrollable element

To correctly animate layout within a scrollable container, you must add the layoutScroll prop to the scrollable element. This allows Motion to account for the element's scroll offset.

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

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

Because layout animations use transform: scale(), they can sometimes visually distort children or certain CSS properties.

  • Child elements: To fix distortion on direct children, these can also be given the layout prop.

  • Border radius and box shadow: Motion automatically corrects distortion on these properties, but they must be set via the style, animate or other animation prop.

<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.

Animations don't work during window resize

Layout animations are blocked during horizontal window resize to improve performance and to prevent unnecessary animations.

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.

Motion's layout animations vs 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.

Motion makes it simple to animate an element's size and position between different layouts. With the layout prop, you can perform layout animations, and by using layoutId you can create seamless "magic motion" effects between two separate elements.

In this guide, we'll learn how to:

  • Animate layout changes with a single prop.

  • Create shared element transitions between components.

  • Explore advanced techniques.

  • Troubleshoot common layout animation issues.

  • Understand the differences between Motion and the native View Transitions API.

How to animate layout changes

To enable layout animations on a motion component, simply add the layout prop. Any layout change that happens as a result of a React render will now be automatically animated.

<motion.div layout />

Layout animation 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" />

It can handle anything from microinteractions to full page transitions.

Note: Layout changes should happen statically via style or className, not via animation props like animate or whileHover, 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:

Performance

Animating layout is traditionally slow, but Motion performs all layout animations using the CSS transform property for the highest possible performance.

Shared layout animations

For more advanced shared layout animations, layoutId allows you to connect two different elements.

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

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

If the original component is still on the page when the new one enters, they will automatically crossfade.

To animate an element back to its origin, you can use the AnimatePresence component to keep it in the DOM until its exit animation has finished.

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

Customise a layout animation

Layout animations can be customised using the transition prop.

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

If you need to set a transition specifically for the layout animation while having a different transition for other properties (like opacity), you can define a dedicated layout transition.

<motion.div
  layout
  animate={{ opacity: 0.5 }}
  transition={{
    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>
</>

Advanced use-cases

Animating within scrollable element

To correctly animate layout within a scrollable container, you must add the layoutScroll prop to the scrollable element. This allows Motion to account for the element's scroll offset.

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

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

Because layout animations use transform: scale(), they can sometimes visually distort children or certain CSS properties.

  • Child elements: To fix distortion on direct children, these can also be given the layout prop.

  • Border radius and box shadow: Motion automatically corrects distortion on these properties, but they must be set via the style, animate or other animation prop.

<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.

Animations don't work during window resize

Layout animations are blocked during horizontal window resize to improve performance and to prevent unnecessary animations.

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.

Motion's layout animations vs 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.

Related topics

Motion+

Motion+

Motion+

Level up your animations with Motion+

Unlock the full vault of 290+ Motion examples, premium APIs, private Discord and GitHub, and powerful VS Code animation editing tools.

One-time payment, lifetime updates.

Motion is supported by the best in the industry.

Stay in the loop

Subscribe for the latest news & updates.

Stay in the loop

Subscribe for the latest news & updates.