Export
The Motion dialect
Your component comes back with initial, animate and a transition per property on each element you animated. A spring that settles inside its own segment exports as a native spring config instead of sampled keyframes, so Motion runs the spring you designed.
- A transition per property
- Motion lets each animated value carry its own transition, so x can spring while opacity eases. Of the three dialects, that’s the closest to how the timeline works.
- Springs stay springs
- A two-keyframe spring that comes to rest within its span becomes
type: "spring"with the stiffness, damping and mass you set. Nothing is sampled, so the code reads like something a person wrote. - …until they can’t
- A spring cut short by the next keyframe has no native equivalent, since a spring config describes a settle, not a cut. Those export as a values array with explicit
times, sampled closely enough to stay within tolerance. Where the cut lands mid-flight, the last two stops share a time: the spring holds until that moment and jumps, as the timeline does, instead of ramping into the target across its final sample. - Rest thresholds are pinned where it matters
- Motion decides a spring has finished using a window scaled to the initial delta. Where that window is coarser than 1% of the travel, the export sets the engine’s own thresholds instead. Otherwise a 12° skew would get a 0.5° threshold, larger than its overshoot.
- Interaction states ride along
- Hover, press and focus become
whileHoverand friends on the same element the timeline animates. They coexist because a state is a target, not a keyframe. - Your component, with the motion written in
- Every part you animated comes back as its own tag, like
motion.liormotion.div, where it stands in your markup, with its attributes kept and the animation added beside them. A pasted module comes back whole: its imports, helpers and state as they were, withimport { motion }added once. Copy it, paste it over the component, and you’re done. - The component animates itself
- The component’s own animation goes on its root element, where CSS and GSAP put it too. Only a component with more than one root, or one whose root element is also a part you animated, gets a
motion.divaround it. - Where it won’t guess
- A part rendered by a
map, a condition or a component your paste defines isn’t in your markup as written, and neither is one whose markup changed after you picked it. Those come as a named props object and the one line that uses it (<motion.li {...row}>), instead of a guess at which element was meant. - It type-checks
- Variants and props objects end in
satisfies Variantsorsatisfies MotionProps, which keeps their eases and spring types exact for TypeScript. Without it,ease: "easeOut"widens to a string Motion rejects. In a.jsxfile, delete those two words. - Scroll, reduced motion and view transitions too
- The exports that need a hook are written into your component the same way:
useScrolloruseReducedMotionat the top of its body, the imports it lacks added to the ones it has, and the props on its root. For a component with picked parts, reduced motion picks each part’s props per render (variants={reduce ? itemReduced : item}). An arrow component that returns its markup directly gets a body to hold the hook. - Where one paste stops
- A
refalready on your root, or a spread that might carry one, puts the scroll export on amotion.divaround it. If a hook’s name is already used for something else, or the export is a cross-fade on a pasted module, the component is left for you to fill in. A cross-fade needs aviewKeyprop, and your component’s signature isn’t the export’s to change.