Shots in this scene
MotionConfig, Reduced Motion, Vanilla animate and React Spring
In one line
Motion ignores the reduced-motion setting unless you wrap it in MotionConfig reducedMotion="user"; then movement turns instant while fades and colours still animate. LazyMotion trims the bundle, animate() works without React, and React Spring is the physics-first alternative.
Think of it like this
A building with a master volume switch. Each room has its own speaker, but the switch at the entrance can turn every speaker down at once. MotionConfig is that switch for every motion element below it.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- MotionConfig
- A wrapper that sets defaults (including the reduced-motion policy) for every Motion component inside it.
- reducedMotion
"user","always"or"never": whether Motion follows the device's reduce-motion setting. The default is"never".- LazyMotion
- A wrapper that loads Motion's features separately, used with the lighter
mcomponents. - domAnimation / domMax
- The feature bundles for
LazyMotion: animations and gestures, and the same plus drag and layout. - animate()
- Motion's function for animating elements or values outside React.
- tension / friction
- React Spring's names for a spring's stiffness and damping.
Step by step
01Run it as a normal user
Press Go with the mode on user on an ordinary device. The box slides and turns with a spring, and fades over 300 ms.
02Switch on Reduced motion
Press the timeline's Reduced motion switch. The page reloads as a reduced-motion user. Press Go: the box is at its final place at once, while the opacity track still runs. The console shows Motion's warning.
03Compare the three settings
Choose always on a normal device: movement is instant. Choose never with Reduced motion on: everything animates, ignoring the device. The select changes only the config, so the page itself stays the same.
04Check the cost
Run the vanilla example: animate() makes one transform track per list item. Run the React Spring one: the Timeline stays empty while the box moves.
Try it yourself
- 1
Read the three modes
Run the first example with Reduced motion off and set the select to
always, thennever; then switch Reduced motion on and tryuser,always,never. Measured:userandalwaysleave only an opacity track (the transform is alreadymatrix(0, 1, -1, 0, 180, 0)at 50 ms);neverruns a 500 ms transform track and a 450 ms rotation track even on a reduced device. - 2
Make the box skip the config
Remove
<MotionConfig>and run as a reduced-motion user. Measured at 170 ms in Topic 15.1's example: the box is at 179.2 px, the same as for a user with full motion. Nothing in Motion follows the device until you add the wrapper.
Code & diagrams
Go moves, turns and fades the box. Switch the timeline's Reduced motion on and try each setting in the select: only `user` and `always` make the movement instant.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.No React. Three list items enter with a stagger; the button wobbles them. `animate(".nothing", ...)` quietly does nothing.
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 right after the call: 3
missing target: 0
finished: li 3 opacity 1The box moves on `tension` and `friction`. The page presses Go itself and counts the browser animations on the Timeline: there are none.
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
moving, animations on the timeline: 0Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Trust the CSS media query for Motion
Add @media (prefers-reduced-motion: reduce) { .box { transition: none; animation: none; } } to the example and remove MotionConfig.
Myth vs fact
Myth
Motion respects prefers-reduced-motion automatically.
Fact
Not unless you opt in: the default of MotionConfig is reducedMotion: "never". Without a wrapper, Motion ignores the device.
Myth
LazyMotion always shrinks the bundle.
Fact
domAnimation saves about 14 KB gzip here. Once you add layout or drag with domMax, you carry nearly the full size (105.4 KB vs 105.3 KB).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
MotionConfigalso sets a defaulttransitionfor everything below it:<MotionConfig transition={{ duration: 0.2 }}>gives a whole feature the same timing, which suits motion tokens (Phase 18). - ▸
Reduced motion is a setting you can lose track of: check that the transform is final before any opacity ends, as the check in the exercises does. A reduced user still needs a visible change, which the opacity tween gives.
- ▸
Bundle numbers depend on the bundler and tree-shaking. The
motion/minifigure (3.4 KB) applies only toanimate()and WAAPI features: no springs, nostagger, no React components.
Remember this
- 1
The problem this solves: the CSS rules of Phase 7 cannot reach animations that JavaScript runs. Motion's transform animations are WAAPI or JavaScript, so
@media (prefers-reduced-motion)does not stop them. Before this topic, a Motion page ignores the person who asked for less motion. Measured: the first example of Topic 15.1, run as a reduced-motion user, still springs to 179 px. - 2
**The default is
never.**MotionConfig'sreducedMotioncan be"user"(follow the device setting),"always"or"never", and the default, read from the installed source, is"never". Wrap your app once:<MotionConfig reducedMotion="user">. The configurator does exactly this, with the site's own setting from Phase 7 on top. - 3
What it changes. With the setting active, values that move something (every transform:
x,y,scale,rotate,skew; andwidth,height,top,left,right,bottom) jump to their target at once, and layout animations are instant. Everything else keeps animating. Measured on the example below, as a reduced-motion user: a box animatingx,rotateandopacityhad its transform at the finalmatrix(0, 1, -1, 0, 180, 0)after 50 ms with no transform track, while opacity still ran a 300 ms tween. This is Phase 7's rule, reduce rather than remove, built in."always"on a normal device behaves the same;"never"on a reduced device animates everything. - 4
What it does not change. Drag keeps working, and the release spring still runs (Topic 15.5). Animations you start yourself with
animate()are yours to guard. In development Motion logsYou have Reduced Motion enabled on your device. Animations may not appear as expected.. For more information and steps for solving, visit https://motion.dev/troubleshooting/reduced-motion-disabledwhen the device is reduced and Motion reads it (even withnever, or when onlyuseReducedMotion()reads it), and when you setalways(measured; a normal device withuserorneverprints nothing), so a developer who tests with reduced motion sees why things jump. - 5
LazyMotion. The full
motioncomponent carries every feature.LazyMotionwithmcomponents loads features separately:domAnimation(animations, variants, exit, hover, tap, focus) ordomMax(adds drag and layout). Measured by bundling a tiny entry file for each case with esbuild (--bundle --minify, production React) against the installed packages, then gzipping the output: React alone 60.2 KB; React plusmotion.div105.3 KB (45.1 KB more); React plusLazyMotion,domAnimationandm.div91.1 KB (30.9 KB more); withdomMax105.4 KB (45.2 KB more). SodomAnimationsaves about 14 KB, and a page that uses layout or drag saves nothing. Totals move by a few KB with the bundler, its version and its flags, so trust the differences, not the totals.featurescan also be an async function that imports the bundle later, andstrictmakes a straymotioncomponent throw. - 6
**Vanilla
animate().** Outside React,animate("li", { x: [0, 20, 0] }, { duration: 0.3 })takes a selector or element, returns controls (finished,pause,cancel,time), and uses the same hybrid engine: threelimade threetransformtracks of 300 ms (measured).stagger(0.1)spreads delays. A selector that matches nothing is not an error: it returns controls with adurationof 0 (measured). Bundled alone it is 24.0 KB gzip;motion/mini(WAAPI only) is 3.4 KB (same esbuild measurement). - 7
React Spring, in one section. React Spring models everything as springs (
tension,friction,mass): there is noduration.useSpringreturns animated values thatanimated.divwrites to the element's style from JavaScript on each frame, with no React re-render. It never uses WAAPI (measured: 0 animations on the Timeline while the box moved), so every frame is main-thread work. It bringsuseTransitionfor mounting and leaving (likeAnimatePresence),useTrailanduseChain, and adds about 17.0 KB gzip over React foruseSpringandanimated(measured). It has no built-in layout or shared-element animation. Choose it when physics and gestures lead the design; choose Motion when you want layout animation, exits, variants and WAAPI.
Explain it without notes
Why does a Motion page ignore reduced motion by default, and how do you fix it?
When would you choose LazyMotion, and when would it not help?
Practice
Make the app follow the device's reduced-motion setting, and also a site toggle that can force "reduced" or "full".
Switch an app's motion.div components to m.div and load the animation features after the first paint.
Trade-offs
- ↔
Motion gives layout animation, exits and WAAPI at about 31 to 45 KB gzip over React. React Spring is smaller (about 17.0 KB over React) and physics-pure, but has no layout animation and no browser animations.
- ↔
Instant movement under reduced motion changes what a person sees: design the end state to be clear without the journey (Phase 7).
Done when you can
You can wrap an app so Motion follows the device's reduced-motion setting
You can say what
reducedMotionchanges and what it leaves aloneYou can choose between
motionandLazyMotionwithdomAnimationordomMax, and quote the size differenceYou can use vanilla
animate()and say where React Spring fits