Should you use Framer Motion or Motion One?

Photo of Matt Perry
Matt Perry / June 2022

Hello everyone. My name's Matt Perry and I have an addiction to maintaining JavaScript animation libraries.

One, Framer Motion is a declarative React animation library, whereas Motion One is an framework-agnostic library built atop the browser's native Web Animations API (WAAPI).

Two different libraries with two different sets of design goals. But, since the release of the latter, I've been asked many times by React developers which they should use in their projects.

TLDR: You almost certainly want to use Framer Motion!

This question comes up way more than I anticipated. Enough to think that I'm probably too close to the problem of UI animation. Which means there's probably an interesting opportunity here to write about some things that I've taken for granted, like the respective design philosophies of the two libraries, their opposing limitations/trade-offs, and the broader differences between declarative and imperative animation.

So to start, let's take a brief look at the inception and fundamental design goals of each library.

Framer Motion in a nutshell

Today, Framer is an incredible website creator with the user experience and flexibility of a design tool like Figma or Sketch. You can achieve amazing results with zero code.

However, back in 2018, Framer was a prototyping tool that allowed you to bring life to your flat designs using code. Code wasn't something you could add for a little flourish like it is today, all animations had to be created with it.

Framer's audience primarily consists of people either who don't code or don't code as their primary job, and yet the only way to animate back then was to code. Not ideal.

So, to make animation as easy as possible, the primary design philosophy of Framer Motion was, and continues to be, simplicity.

At the time, Framer had been freshly rebuilt atop React. This allowed us to write a declarative API, where animations triggered on mount and via gestures can be defined as simple descriptions:

  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  whileHover={{ scale: 1.2 }}

This API avoids the need for developers to interact with the DOM via selectors, refs or other kinds of error-prone, severable wiring.

It also abstracts much of the busyness typically associated with traditional JavaScript animation libraries and brings the experience of adding features like spring animations, mount/unmount animations, gesture animations etc, much closer to CSS.

In franker words, there is much less stuff to fuck up.

The great thing about a declarative API like this, is features are much quicker to write, easier to read, more robust to maintain, and therefore cheap to iterate or delete.

Motion One-oh-one

About a year ago I was kicking the tires of WAAPI. It's essentially a programmatic interface to many of the features offered by CSS transitions and animations, going beyond in some areas.

I had originally wanted to write an educational course on it, but I kept running into cross-browser inconsistencies, implementation bugs (predominantly in Safari), and strange design choices.

Additionally, the API falls short of many features you'd expect of a traditional JavaScript animation library like Greensock. For instance, there's no support for springs, animating transforms independently, or sequencing complex timelines.

And yet, it has some amazing potential upsides. Unlike requestAnimationFrame, the backbone of traditional JS animation, it is uniquely capable of running animations on the GPU.

Further, it can animate between value units, like px and %, rgba and hsla, or calc() and var(), for free. With JS animations this can only otherwise be achieved with (sometimes significant) bundlesize and runtime overhead.

I felt like there were enough benefits to justify a library that smoothed out WAAPI's rough edges, enhanced its capabilities, while keeping bundlesize to a minimum. So the design goal of Motion One is to be the jQuery of WAAPI.

As a result, it achieves much of the same feature set as libraries like Greensock, in some cases furthering it, but for just 15% of the kb.

Yet, being based on WAAPI means there's some things it will simply never be able to do, most notably the ability to access values every animation frame. Once they're in the WAAPI black box, they're not coming out until the animation is finished. This is a real flipside to requestAnimationFrame, where direct access to values is intrinsic.

Design goals and compromise

For every library I write, I try and understand its primary role and essence through a simple design goal.

The same way in the Python world they might say something is "pythonic" when it aligns with the nature and philosophy of Python, each new API for a given library should be created in service of the design goal.

For Framer Motion, this has been simplicity. For Motion One, the jQuery of WAAPI.

The most important use of a clear design goal is to help me decide which way to call it when I encounter a compromise.

For instance, my favourite feature of Framer Motion is its "sensible defaults".

If no transition is explicitly defined, then Framer Motion will generate one based on the type of value being animated. In broad strokes, independent transforms get some kind of spring and everything else, like opacity/color, are animated with a duration-based easing.

This means animating independent transforms with a spring easing on hover looks like this:

<motion.div animate={{ rotate: 90 }} whileTap={{ scale: 0.9 }} />

Which is simpler than even CSS. By default, this animation will feel pretty good, even if you know nothing about animation.

However, to achieve an API like this, the code to run these springs and custom easings must be included in the overall bundlesize. Many little choices like this add up.

By contrast, Motion One's design goal is to push WAAPI beyond its core capabilities, but keeping an eye on bundlesize. Everything should be small, or at least tree-shakable.

When animating independent transforms with a spring, spring must be imported explicitly. So if you never use it, users will never download it.

To achieve the same rotation animation, with a scale animation on press, the code will look roughly like this:

import { animate, spring } from "motion"

const element = document.getElementById("my-element")

animate(element, { rotate: 90 }, { easing: spring() })

element.addEventListener("pointerdown", startPointerDownAnimation)

function startPointerDownAnimation(event) {
    { scale: 0.9 },
    { easing: spring({ stiffness: 300, damping: 30 }) }

  window.addEventListener("pointerup", startPointerUpAnimation)

function startPointerUpAnimation(event) {
  animate(event.currentTarget, { scale: 1 }, { easing: spring() })

  window.removeEventListener("pointerup", startPointerUpAnimation)

Now, you'll be forgiven for reading this code and thinking it isn't the explicit spring import that makes the biggest difference here, and you'd be right.

In fact even this code, as verbose as it is, is really only low-res approximation of what Framer Motion does under the hood to provide things like lifecycle management, iOS 13 compatibility for pointer events, filtering non-primary pointers and annotating events with extra pointer data.

Which brings me to the primary point I want to make in this post. "Framer Motion or Motion One?" is a false dichotomy. Framer Motion is a declarative, high-level library whereas Motion One is an imperative, low-level one. They're different beasts, with different use cases.

Declarative vs imperative code

What do I mean by declarative vs imperative? If you've ever written HTML you might already have an intuitive sense. Look at this:

  <p>Hello world!</p>

This doesn't appear in a browser like magic, the browser takes this markup, this description, this declaration, and converts it to a series of instructions that end up drawing the page you see on screen.

Look at how we'd build this same markup in JavaScript:

const newParagraph = document.createElement("p")
newParagraph.innerText = "Hello world!"

Notice how the HTML is a description, whereas the JavaScript is a series of instructions.

The HTML can be said to be more declarative whereas the JavaScript can be said to be more imperative.

I say "more" because I don't think there's a hard and fast boundary between the two, it's more of a gradient. If a real low-level developer, who deals daily with memory allocation and CPU instructions, is reading this they would positively balk at my description of JavaScript as being imperative because so much work is implicit in a seemingly simple command like element.innerText = "".

So to answer your question

In my opinion there's nothing intrinsically better about declarative or imperative APIs, but depending on your use case one can usually be preferred.

For instance, if you're already splashing around in the world of direct DOM manipulation then an imperative library like Motion One is certainly going to fit your usage better. It gives you the maximum freedom and composability.

Whereas if you're a Vue or Solid developer than the Motion One APIs specifically built to live in those declarative worlds are going to leave your code cleaner and more robust than using the framework-specific escape hatches to tie directly into the imperative APIs.

And so the same is true of Framer Motion.

By asking "Framer Motion or Motion One?" you're asking whether you want your absolute simplest animation to look like this:

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

Or to look like this:

const ref = useRef(null)

useEffect(() => {
  animate(ref.current, { scale: 2 })
}, [])

return <div ref={ref} />

With the former scaling neatly as you add more functionality by sprinkling over more props and the latter getting more complicated as you try an interweave various side effects and event listeners while juggling refs.

Remember too that you're always free to optionally use the escape hatch of side-effects if you have a more complex use-case than a declarative API allows, for instance if you want to hook into Motion One's timeline sequencing function. But this shouldn't be the bedrock of your animation strategy.

One last caveat

For 99% of React developers I recommend the better developer experience. However, things aren't always so black and white.

It might be that you're in the rare set of developers who absolutely must use only hardware accelerated animations because the majority of your audience is on low-powered devices.

Or you might be on a strict bundlesize budget, you only use animation features, and Framer Motion's ability to lazy-load animations and tree-shake features still doesn't crank the bundlesize down far enough.

In these cases we come back to the compromise. If these are your fundamental design goals, then the compromise is to forego the DX and features, and in that case then Motion One is a good option.

Why not both?

I mentioned earlier that declarative APIs are at some point converted to a series of imperative instructions and indeed Framer Motion is built upon an existing imperative animation API I made (yes, another) called Popmotion.

(If you're interested, the design goal of Popmotion is to animate anything anywhere, so it is headless, unopinionated and low-level. So the compromise is it's better consumed via a more opinionated library like Framer Motion or some other custom wrapper.)

I also mentioned that declarative Vue and Solid APIs exist for Motion One.

So why not build Framer Motion on Motion One instead?

Well, it's something that could come in the future. Motion One is small enough that it could be possible to use it in specific situations, like running GPU animations for specific values.

It's so small we may even see bundlesize savings and performance improvements for using it only to animate between different value types or very complex shadows/filters, where we currently have custom code to achieve these things.

But WAAPI has enough restrictions that on balance it probably doesn't make sense to move over to Motion One wholesale. Framer Motion has an incredible set of features, and even some of the most mundane - like custom easing functions - aren't supported by WAAPI.

Now that Framer is so capable at producing incredibly advanced animations with zero code, I think there's some room to loosen the "simplicity" design goal just a little, to find some compromise with extra composability and more natural tree-shakability so we can find the best of all worlds.