Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 15.1Advanced ~22 min · shot 1 of 7

The motion Component

In one line

A motion.div is a normal element that animates to whatever its animate prop says. Motion picks a spring for movement and a short tween for the rest, runs what it can through the browser's own animation engine, and drives the rest from JavaScript.

0/7 · 0%

Think of it like this

A thermostat. You set the target, 21 degrees. You do not say "run the heater for four minutes"; the thermostat works out how to get there and adapts if you change the target halfway. animate={{ x: 160 }} is the target. Motion works out the path.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

motion component
An element such as motion.div that renders like the plain tag and accepts animation props (animate, initial, transition).
motion value
An object that holds one animated number (or colour). Changing it updates the element's style without a React re-render.
WAAPI
The Web Animations API: the browser's own animation engine. Motion uses it for transform, opacity, filter, clipPath and backgroundColor.
spring
An animation driven by physics (stiffness, damping) instead of a fixed duration. Phase 9 covers the maths.
hybrid engine
Motion's mix of browser animations (WAAPI) where they can do the job and its own JavaScript loop where they cannot.
keyframes
More than one value for a property, such as x: [0, 100, 50]; the animation passes through each in turn.

Step by step

01Run it and press Go

The console says animations at load: 0: the box starts at its target. Press Go. Three tracks appear: opacity and background-color (300 ms each) and transform (500 ms), all green.

02Pause and step the move

Press Go, Pause, then Step. The box passes 160 px on the way, and measured at 170 ms it is at 179.2 px (112 percent): the spring overshoots, then settles. Opacity has no overshoot because it is a tween.

03Make it a tween

Add transition={{ duration: 0.3 }} to the box. Now all three tracks last 300 ms with ease-out, and the box stops at exactly 160 px with no overshoot. A duration turns a spring into a tween.

04Lose the tracks

Add onUpdate={() => {}}. The tracks disappear: Motion now writes the style each frame. The box still moves, so only the Timeline shows the change (see Break it).

Try it yourself

  1. 1

    Turn the spring into a tween

    In the first example add transition={{ duration: 0.3 }} to the box. Measured: all three tracks last 300 ms with ease-out, and at 170 ms the box is at 120.5 px and rising, and at 300 ms it is at exactly 160, with no overshoot.

  2. 2

    Make the spring bouncier

    Add transition={{ type: "spring", bounce: 0.6 }}. Measured: the transform track now lasts 800 ms (it was 500), and at 170 ms the box is at 199.8 px, about 25 percent past its 160 px target; it is back at 158 px at 300 ms and 152 px at 400 ms (it swings below the target before settling). Step through to see it swing.

Code & diagrams

The motion.div and its defaults

Go starts three animations: a spring on transform (500 ms), and 300 ms tweens on opacity and background colour. Pause and step to see the overshoot.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

animations at load: 0
Height to auto, and removal mid-animation

Toggle opens the panel to its real height. There is no Timeline track: height is driven from JavaScript. Remove it while it moves: no error, no warning.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

height at load: 0px
Motion values do not re-render

Three `set` calls change what is on screen, and the component function ran once.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

x = 200, opacity = 1, renders: 1

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Add onUpdate to a harmless box

In the first example add onUpdate={() => {}} to the motion.div.

terminal
$ Press Go and read the Timeline
── what you'll see ──
The box still moves, but the Timeline shows no tracks (measured at 170 ms and 400 ms: no animations). At 170 ms the box is at 10 px and opacity is 0.42 (with WAAPI: 179 px and 0.89), and at 300 ms it is at 178 px with opacity 0.92 (with WAAPI: 159 px and 1). The values differ slightly between runs because the start frame varies.

Myth vs fact

Myth

Framer Motion and Motion are two different libraries.

Fact

Motion is the same library under its new name. Old code imports from framer-motion; new code imports from motion/react. Version 14.3.0 of framer-motion and motion are published together, and motion/react is the maintained import.

Myth

animate always runs on the GPU.

Fact

Only the values on the WAAPI list do, and only when nothing blocks it. height, width and borderRadius run in JavaScript, every frame.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    The spring for a transform value is sampled into one linear() easing on a 500 ms or similar animation, so the Timeline's value graph shows the real curve. If a spring needs a long tail, the browser animation lasts as long as Motion's duration estimate, not as long as the visible motion.

  • ▸

    Motion values are the escape from React's render loop: useTransform and useSpring chain without state. A scroll position, a pointer position and a drag offset all enter the same way, so a parallax or a cursor follower costs no re-renders.

  • ▸

    animate can also be an array of keyframes, with times for where each one lands. With more than two keyframes the default becomes the 0.8 s keyframes transition, so a longer list is slower unless you set duration.

Remember this

  1. 1

    The problem this solves: in React, changing state swaps the screen in one frame. CSS transitions can soften a few properties, but they cannot reach height: auto, cannot use spring physics, cannot keep an element on screen after React removes it, and need class names for every state. Hand-written animation code is worse: it fights React's renders. Motion keeps the animation next to the state that causes it.

  2. 2

    The component. motion.div (and motion.button, motion.svg, and so on for every HTML and SVG tag) renders the element you expect and adds props: animate is the target, initial is where it starts, transition says how to get there. On the first render the element starts at its animate values and nothing runs (measured: animations at load: 0). initial={false} says the same on purpose; initial={{ opacity: 0 }} makes it animate in. When animate changes, only the values that changed move, from where they are now, so an interrupted move continues from its current value.

  3. 3

    Defaults, from the installed source. With no transition, Motion chooses per value. Transform values (x, y, rotate) use a spring with stiffness: 500, damping: 25. scale uses a critically damped spring (stiffness: 550, damping 30, or about 46.9 when the target is 0). Everything else (opacity, colours, height) uses a 0.3 s tween with the curve [0.25, 0.1, 0.35, 1]. A list of more than two keyframes takes 0.8 s. These differ from the explicit spring default (stiffness: 100, damping: 10, Phase 9) that applies when you write type: "spring" yourself.

  4. 4

    Two engines. Motion is hybrid. For opacity, clipPath, filter, transform (all of x, y, rotate, scale) and backgroundColor, it hands the animation to the browser as a Web Animation (WAAPI), which can run off the main thread (Phase 6). Springs are sampled once into a linear() curve, which is why the Timeline shows a 500 ms transform track with a long curve. For everything else (height, width, borderRadius) and whenever WAAPI cannot be used, Motion runs its own loop on requestAnimationFrame and writes styles every frame. No browser animation exists then, so the Timeline shows no track.

  5. 5

    When WAAPI is skipped. The source refuses WAAPI when the element has an onUpdate callback (there is no way to read a WAAPI value each frame), a transformTemplate, a repeat delay or repeatType: "mirror", a spring with damping: 0, or an inertia animation. Adding onUpdate to a harmless box is a common way to lose compositor animation without noticing.

  6. 6

    Motion values. Every animated value is a motion value: a small object holding the current number. useMotionValue(0) makes one, useTransform(x, [0, 200], [0.3, 1]) derives another, and style={{ x, opacity }} binds them to the element. Changing a motion value updates the element's style directly without re-rendering React. The example below calls x.set three times, and React rendered once.

  7. 7

    Edge cases. animate={{ height: "auto" }} works: Motion measures the real height first, then animates the number (a layout property, driven from JavaScript, so there is no Timeline track). If the component unmounts mid-animation, the animation stops with the element, with no console warning (measured).

Explain it without notes

01

What does Motion do when no transition is given, and why does x behave differently from opacity?

02

Which values does Motion run as browser animations, and what makes it fall back to JavaScript?

Practice

01

Make a card that fades in and slides up 20 px when it mounts, with a 0.4 s tween and no spring.

02

Show a counter that counts from 0 to a target in 0.8 s without re-rendering on every frame.

Trade-offs

  • ↔

    Motion's default springs feel natural, but a spring's end is hard to predict: for exact timing (a 300 ms sync with other things) use duration, which gives a tween.

Done when you can

  • You can say which defaults Motion uses for x, scale and opacity

  • You can tell from the Timeline whether a Motion animation runs through WAAPI

  • You can animate to height: "auto" and say what it costs

  • You can drive a value with a motion value and no React state