Shots in this scene
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.
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.divthat 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,clipPathandbackgroundColor. - 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
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 withease-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
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
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.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
animations at load: 0Toggle 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.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
height at load: 0pxThree `set` calls change what is on screen, and the component function ran once.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
x = 200, opacity = 1, renders: 1Break 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.
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:
useTransformanduseSpringchain 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. - ▸
animatecan also be an array of keyframes, withtimesfor 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 setduration.
Remember this
- 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
The component.
motion.div(andmotion.button,motion.svg, and so on for every HTML and SVG tag) renders the element you expect and adds props:animateis the target,initialis where it starts,transitionsays how to get there. On the first render the element starts at itsanimatevalues and nothing runs (measured:animations at load: 0).initial={false}says the same on purpose;initial={{ opacity: 0 }}makes it animate in. Whenanimatechanges, only the values that changed move, from where they are now, so an interrupted move continues from its current value. - 3
Defaults, from the installed source. With no
transition, Motion chooses per value. Transform values (x,y,rotate) use a spring withstiffness: 500, damping: 25.scaleuses 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 writetype: "spring"yourself. - 4
Two engines. Motion is hybrid. For
opacity,clipPath,filter,transform(all ofx,y,rotate,scale) andbackgroundColor, 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 alinear()curve, which is why the Timeline shows a 500 mstransformtrack with a long curve. For everything else (height,width,borderRadius) and whenever WAAPI cannot be used, Motion runs its own loop onrequestAnimationFrameand writes styles every frame. No browser animation exists then, so the Timeline shows no track. - 5
When WAAPI is skipped. The source refuses WAAPI when the element has an
onUpdatecallback (there is no way to read a WAAPI value each frame), atransformTemplate, a repeat delay orrepeatType: "mirror", a spring withdamping: 0, or aninertiaanimation. AddingonUpdateto a harmless box is a common way to lose compositor animation without noticing. - 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, andstyle={{ x, opacity }}binds them to the element. Changing a motion value updates the element's style directly without re-rendering React. The example below callsx.setthree times, and React rendered once. - 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
What does Motion do when no transition is given, and why does x behave differently from opacity?
Which values does Motion run as browser animations, and what makes it fall back to JavaScript?
Practice
Make a card that fades in and slides up 20 px when it mounts, with a 0.4 s tween and no spring.
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,scaleandopacityYou can tell from the Timeline whether a Motion animation runs through WAAPI
You can animate to
height: "auto"and say what it costsYou can drive a value with a motion value and no React state