Edit Page

#Migrate from Pose to Motion

How to convert a Pose project to Framer Motion.

Framer Motion is the successor to the Pose animation library. Like Pose, it provides a declarative API to power animations and gestures in your React app. But Motion attempts to make the API even simpler for the simplest cases, yet more flexible to handle the most advanced.

In this guide we'll explain how to migrate a Pose project to Motion.

#posed vs motion

The primary API change is that in Pose, components had to be pre-made outside the render function.

const MyComponent = posed.div(config)

This proved to be very inflexible. In Motion, motion components are called directly within the render function itself.

<motion.div />

#Poses vs variants

Poses exist in Framer Motion under the name "variants". In Pose, they had to be defined outside of the render function, pre-defined in a component.

// Pose
const Posed = posed.div({
  visible: { opacity: 1 },
  hidden: { opacity: 0 },
})

const MyComponent = () => {
  return <Posed initialPose="hidden" pose="visible" />
}

Variants work in much the same way as poses, only now they can be defined anywhere and passed in via the variants prop.

// Motion
const MyComponent = () => {
  const variants = {
    visible: { opacity: 1 },
    hidden: { opacity: 0 },
  }

  return (
    <motion.div
      initial="hidden"
      animate="visible"
      variants={variants}
    />
  )
}

Of course, in Framer Motion you only have to use variants if you're performing animations throughout the tree. Otherwise you can set props to animate directly.

<motion.div animate={{ opacity: 0 }} />

#Dynamic poses

Each value within a Pose could be set as a function, to dynamically return a different value based on the props passed to that component.

const Posed = posed.div({
  visible: {
    opacity: ({ target }) => target,
  },
  hidden: { opacity: 0 },
})

const MyComponent = () => {
  return (
    <Posed initialPose="hidden" pose="visible" target={1} />
  )
}

This came with the limitation that the pose itself couldn't be dynamic. The values themselves could change, but we couldn't return different poses with different values.

In Motion, because variants can be defined within the render function, it might be that you don't even need dynamic variants. But because the same variants definition can be used for multiple components, they are often still useful.

So in Motion, entire variants are dynamic, so they can return entirely different definitions based on the data received. This data is now passed through custom to ensure type safety for the rest of the component's props (as the previous approach required very permissive types).

// Motion
const MyComponent = () => {
  const variants = {
    visible: target => ({ opacity: target }),
    hidden: { opacity: 0 },
  }

  return (
    <motion.div
      initial="hidden"
      animate="visible"
      variants={variants}
      custom={1}
    />
  )
}

#applyAtStart/applyAtEnd

Pose offered the applyAtStart and applyAtEnd for setting properties at the start and end of a transition.

const Posed = posed.div({
  visible: {
    opacity: 1,
    applyAtStart: {
      display: "block",
    },
  },
  hidden: {
    opacity: 0,
    applyAtEnd: {
      display: "none",
    },
  },
})

In Motion, non-animatable properties are automatically applied at the start of an animation. Anything that you want to be applied at the end can be set to transitionEnd.

const variants = {
  visible: {
    opacity: 1,
    display: "block",
  },
  hidden: {
    opacity: 0,
    transitionEnd: {
      display: "none",
    },
  },
}

#Durations

Durations and delays in Pose were set in milliseconds. In Motion, they're set in seconds.

// Pose
const duration = 1000

// Motion
const duration = 1

#Transition

Orchestration props like staggerChildren have now moved underneath transition.

const variants = {
  visible: {
    opacity: 1,
    display: "block",
    transition: {
      staggerChildren: 0.1,
    },
  },
}

In Pose, there were both beforeChildren and afterChildren boolean props. In Motion, there's just one: when. This can be set to either "beforeChildren" or "afterChildren".

You can also set a default transition for a component via the transition prop.

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

#Mount animations

In Pose, to animate a component on mount, you had to set the initialPose prop.

<PosedComponent initialPose="hidden" pose="visible" />

In Motion, components will always animate on mount if the content of animate is different to initial, or the value read from the DOM (as set by CSS).

<motion.div animate={{ scale: 2 }} />

This mount animation can be suppressed by setting initial to false.

<motion.div initial={false} animate={{ scale: 2 }} />

Unlike Pose, this initial state will also be rendered server side, so no more flashes of styles.

#Hoverable/Pressable

Pose provided shortcuts to animate a component while certain gestures were active. For instance to respond to hover gestures with an animation, you had to set hoverable to true and provide a "hover" pose.

const PosedComponent = posed.div({
  hover: { scale: 1.1 },
  hoverable: true,
})

This had a couple of limitations in that the name of the hoverable pose had to be "hover". Because of this, we couldn't use the presence of the "hover" pose to tell whether this specific component should respond to hovers, or we were just defining a pose to animate as a result of a hover over a parent component.

Instead, Motion provides the whileHover and whileTap props instead. These can be set either as an object of values, or the name of a variant.

<motion.a whileHover={{ scale: 1.1 }} />

#Draggable

In Pose, a component could be made draggable by setting its config to draggable: true | "x" | "y". This was inconvenient because once defined, this configuration couldn't be changed.

posed.div({ draggable: "x" })

In Motion, drag is a prop that can be changed like any other.

<motion.div drag />

There was also a special "drag" pose to animate to while dragging was active. But in Motion the whileTap prop can be used, as this shares the same lifecycle.

#Drag boundaries

In Pose there was a dragBounds option that could define the boundaries of the draggable area. It could define top/left/bottom/right boundaries in pixels or a percentage.

In Motion, the dragConstraints prop can define boundaries only in pixels.

<motion.div
  drag="x"
  dragConstraints={{ left: 0, right: 100 }}
/>

But now there's also the ability to pass in a ref to another component, and have that act as the drag constraints for this component. So if that component is styled responsively, the drag constraints will update when the viewport size changes.

const MyComponent = () => {
  const constraintsRef = useRef(null)
  return (
    <>
      <div ref={constraintsRef} />
      <motion.div drag dragConstraints={constraintsRef} />
    </>
  )
}

These constraints are enforced elastically now, but we can remove this effect by setting dragElastic to 0.

<motion.div
  drag
  dragConstraints={constraints}
  dragElastic={0}
/>

#Passive values

Pose allowed you to passively animate values based on the output of others.

const Box = posed.div({
  draggable: "x",
  passive: {
    y: ["x", latestX => latestX * 2],
  },
})

You could attach values either to others on the same component, or those higher up the hierarchy via labels.

This could be useful but the strict hierarchal relationship was limiting and the use of labels created brittle, weak dependencies.

Motion tracks the state and velocity of every animating value with a MotionValue. Usually, it creates these by default, but by manually creating them they can be passed between components, and interesting relationships can be formed with the useTransform hook.

import {
  motion,
  useMotionValue,
  useTransform,
} from "framer-motion"

export const Box = () => {
  const x = useMotionValue(0)
  const y = useTransform(x, latestX => latestX * 2)

  return <motion.div drag style={{ x, y }} />
}

#FLIP

In Pose, expensive layout-affecting animations like height and top could be automatically converted to performant animations using transforms like scaleY and y.

const Menu = posed.div({
  open: {
    height: "auto",
    flip: true,
  },
  closed: {
    height: 0,
    flip: true,
  },
})

This had a number of problems and limitations. As we re-introduce this feature into Framer Motion we're trying to ensure these don't exist going forward.

With the new layoutTransition prop, a component will automatically detect when it changes layout as the result of a re-render.

<motion.div layoutTransition />

This layout change can be instigated by any CSS application or DOM change, for instance:

  • Being adjacent to a resized component
  • Adding/removing a CSS class
  • Changing position in the DOM
  • Changing children

So not only does the layout transition capability work in a wider variety of use-cases, it also offers more freedom in how you affect the layout. It's also a much clearer name!

#Scaled content

One of the key limitations of the flip option in Pose was distorted content within components that didn't retain their aspect ratio throughout the layout animation.

This occured because FLIP animations immediately switch to the new layout and then "undo" the layout change using transforms. It then animates to the new layout by animating the transform to none. So any content within here will look stretched and squashed.

In Motion we can maintain the size and shape of children components with the useInvertedScale hook. This provides a scaleX and scaleY MotionValue that automatically compensates for the scale of the parent component.

const Text = () => {
  const inverted = useInvertedScale()

  return <motion.p style={inverted}>No distortion</motion.p>
}

Depending on your layout you'll need to anchor the origin of this scale by choosing originX and originY points as the default transform origin of every component is 0.5. For the effect to work you'll also need to ensure the component is in the same position for both layouts.

#PoseGroup

The final difference between the two libraries is the PoseGroup component. It provided three main functionalities:

  • Unmount animations
  • Reorder animations
  • FLIP-powered unmount

This FLIP animation to unmount has been removed, as it often lead to unexpected or weird results. We may introduce a similar feature in the future. It also wasn't often needed. However the other two have been split into specific functionalities.

#Unmount animations

There's a new component, AnimatePresence, that is responsible for unmount animations. It works in much the same way as PoseGroup, enabling the use of an exit prop that can either be an object or the name of a variant.

<AnimatePresence>
  {isVisible && (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    />
  )}
</AnimatePresence>

#Reorder animations

With PoseGroup, when an item was removed all of the other items would animate smoothly into their new place.

This has been evolved into a new prop called positionTransition, and it doesn't require the use of AnimatePresence to work (but they do work nicely together).

<motion.div positionTransition />

It can be set either as a boolean for a default animation, or as a Transition.

<motion.div
  positionTransition={{ type: "spring", stiffness: 500 }}
/>

If defined, when the motion component changes position on a page as a result of the component re-rendering, it'll use this transition to animate smoothly to its new layout.

Paired with AnimatePresence, you have all the previous functionality of PoseGroup.

<AnimatePresence>
  {items.map(item => (
    <motion.li
      key={item.id}
      exit={{ opacity: 0 }}
      positionTransition
    />
  ))}
</AnimatePresence>