Documentation

Physics-Based Animations

Control spring dynamics, easing curves, duration, and delay to fine-tune how motionwind animations feel.

Motionwind provides transition configuration classes that control how animations play out. These classes compile to Motion's transition prop, giving you spring physics, easing functions, timing, and delay -- all from your className string.

Spring Animations

Add animate-spring to switch from the default tween (duration-based) animation to a spring (physics-based) animation. Springs produce natural motion that overshoots and settles, rather than following a fixed curve over a fixed duration.

Spring vs Default
<!-- Spring animation -->
<div className="animate-initial:scale-50 animate-enter:scale-100 animate-spring">
  Spring
</div>

<!-- Tween animation (default) -->
<div className="animate-initial:scale-50 animate-enter:scale-100 animate-duration-500">
  Tween
</div>

Spring compiles to:

<motion.div
  initial={{ scale: 0.5 }}
  animate={{ scale: 1 }}
  transition={{ type: "spring" }}
/>

When animate-spring is used without additional spring parameters, Motion applies its default spring configuration (stiffness: 100, damping: 10).


Spring Configuration

Fine-tune spring behavior with four configuration classes. These compile to properties on the transition object and only take effect when animate-spring is also present (or when using any spring-related parameter, which implicitly sets the type to spring in Motion).

ClassCompiled propertyControls
animate-stiffness-{n}stiffness: nSpring tension -- higher values create faster, snappier motion
animate-damping-{n}damping: nResistance -- higher values reduce oscillation
animate-mass-{n}mass: n / 10Weight of the animated object -- higher values create slower, heavier motion
animate-bounce-{n}bounce: n / 100Shorthand bounciness (0 = no bounce, 100 = maximum)

How values map

  • stiffness: The class value maps directly. animate-stiffness-400 becomes stiffness: 400.
  • damping: The class value maps directly. animate-damping-25 becomes damping: 25.
  • mass: Divided by 10. animate-mass-10 becomes mass: 1. animate-mass-30 becomes mass: 3.
  • bounce: Divided by 100. animate-bounce-25 becomes bounce: 0.25.

Bouncy Spring

High stiffness with very low damping produces a spring that oscillates several times before settling. Use this for playful, attention-grabbing animations.

Bouncy Spring
<div className="animate-initial:scale-50 animate-initial:opacity-0 animate-enter:scale-100 animate-enter:opacity-100 animate-spring animate-stiffness-600 animate-damping-5">
  Very bouncy
</div>

Compiles to:

<motion.div
  initial={{ scale: 0.5, opacity: 0 }}
  animate={{ scale: 1, opacity: 1 }}
  transition={{ type: "spring", stiffness: 600, damping: 5 }}
/>

The high stiffness (600) creates strong force toward the target, while the low damping (5) provides very little resistance, resulting in visible overshoot and multiple oscillations.


Snappy Spring

High stiffness with moderate damping produces a spring that reaches its target quickly with minimal overshoot. Use this for responsive UI interactions like button presses and toggles.

Snappy Spring
<div className="animate-initial:scale-80 animate-initial:opacity-0 animate-enter:scale-100 animate-enter:opacity-100 animate-spring animate-stiffness-400 animate-damping-25">
  Quick and snappy
</div>

Compiles to:

<motion.div
  initial={{ scale: 0.8, opacity: 0 }}
  animate={{ scale: 1, opacity: 1 }}
  transition={{ type: "spring", stiffness: 400, damping: 25 }}
/>

The damping (25) absorbs most of the spring energy before it can overshoot, producing a fast settle with barely any bounce.


Smooth Spring

Low stiffness with moderate damping produces a gentle, slow-settling spring. Use this for ambient animations, background transitions, or anything that should feel relaxed.

Smooth Spring
<div className="animate-initial:y-30 animate-initial:opacity-0 animate-enter:y-0 animate-enter:opacity-100 animate-spring animate-stiffness-100 animate-damping-15">
  Gentle and smooth
</div>

Compiles to:

<motion.div
  initial={{ y: 30, opacity: 0 }}
  animate={{ y: 0, opacity: 1 }}
  transition={{ type: "spring", stiffness: 100, damping: 15 }}
/>

Spring presets at a glance

PresetStiffnessDampingCharacter
Bouncy6005Lots of overshoot and oscillation
Snappy40025Fast, nearly no bounce
Smooth10015Slow, gentle settle
Default (Motion)10010Moderate bounce, moderate speed

Using mass

Mass affects how "heavy" the animated element feels. Higher mass means slower acceleration and deceleration.

Heavy vs Light
<!-- Light: mass 0.5 (animate-mass-5 / 10) -->
<div className="animate-spring animate-stiffness-200 animate-damping-10 animate-mass-5 ...">
  Light
</div>

<!-- Heavy: mass 3 (animate-mass-30 / 10) -->
<div className="animate-spring animate-stiffness-200 animate-damping-10 animate-mass-30 ...">
  Heavy
</div>

Using bounce

The animate-bounce-{n} class provides a simpler way to control bounciness without tuning stiffness and damping individually. The value ranges from 0 (no bounce) to 100 (maximum bounce), and is divided by 100 in the compiled output.

Bounce Control
<div className="animate-initial:scale-50 animate-enter:scale-100 animate-spring animate-bounce-50">
  bounce: 0.5
</div>

Compiles to:

<motion.div
  initial={{ scale: 0.5 }}
  animate={{ scale: 1 }}
  transition={{ type: "spring", bounce: 0.5 }}
/>

Easing Functions

For tween (duration-based) animations, easing functions control the acceleration curve. Motionwind provides four easing classes that map to Motion's built-in easing values.

Easing Comparison
<div className="... animate-duration-800 animate-ease-in">ease-in</div>
<div className="... animate-duration-800 animate-ease-out">ease-out</div>
<div className="... animate-duration-800 animate-ease-in-out">ease-in-out</div>
<div className="... animate-duration-800 animate-ease-linear">linear</div>
ClassCompiled valueBehavior
animate-ease-inease: "easeIn"Starts slow, accelerates
animate-ease-outease: "easeOut"Starts fast, decelerates
animate-ease-in-outease: "easeInOut"Slow at both ends
animate-ease-linearease: "linear"Constant speed throughout
animate-ease-circ-inease: "circIn"Circular ease in (sharper curve)
animate-ease-circ-outease: "circOut"Circular ease out
animate-ease-circ-in-outease: "circInOut"Circular ease in-out
animate-ease-back-inease: "backIn"Overshoots then comes back (enters with overshoot)
animate-ease-back-outease: "backOut"Anticipation then settles (exits with anticipation)
animate-ease-back-in-outease: "backInOut"Both overshoot and anticipation
animate-ease-anticipateease: "anticipate"Pulls back then shoots forward

Example compiled output:

<motion.div
  initial={{ opacity: 0, x: -40 }}
  animate={{ opacity: 1, x: 0 }}
  transition={{ duration: 0.8, ease: "easeOut" }}
/>

Extended Easing Functions

Beyond the standard CSS-like easings, motionwind provides additional easing curves from Motion's library. These are useful for more expressive animations.

Extended Easings
<div className="... animate-duration-800 animate-ease-circ-out">circ-out</div>
<div className="... animate-duration-800 animate-ease-back-out">back-out</div>
<div className="... animate-duration-800 animate-ease-anticipate">anticipate</div>

Compiled output for back-out:

<motion.div
  initial={{ opacity: 0, x: -40 }}
  animate={{ opacity: 1, x: 0 }}
  transition={{ duration: 0.8, ease: "backOut" }}
/>
  • Circular easings (circ-in, circ-out, circ-in-out) produce a sharper acceleration curve than the standard easings, based on a circular function.
  • Back easings (back-in, back-out, back-in-out) overshoot the target value before settling, creating an elastic feel without using a spring.
  • Anticipate pulls back slightly before moving forward, like winding up before a throw.

Easing classes are ignored when using spring animations, since springs are physics-based and do not follow a timing curve.


Custom Cubic-Bezier

For complete control over the easing curve, use the arbitrary value syntax to define a custom cubic-bezier curve. The four values correspond to the two control points of the bezier curve (x1, y1, x2, y2).

<div className="... animate-duration-600 animate-ease-[0.25,0.1,0.25,1]">
  Custom cubic-bezier
</div>

Compiles to:

<motion.div
  transition={{ duration: 0.6, ease: [0.25, 0.1, 0.25, 1] }}
/>
ClassCompiled value
animate-ease-[0.25,0.1,0.25,1]ease: [0.25, 0.1, 0.25, 1]
animate-ease-[0.42,0,0.58,1]ease: [0.42, 0, 0.58, 1]
animate-ease-[0.6,0.04,0.98,0.335]ease: [0.6, 0.04, 0.98, 0.335]

This allows any custom cubic-bezier curve, matching the same four-value format used by the CSS cubic-bezier() function.


Steps Easing

Steps easing creates a stepped animation with a specified number of discrete steps, rather than a smooth interpolation. This is useful for sprite sheet animations, typewriter effects, or any animation that should jump between values.

<div className="... animate-duration-1000 animate-ease-steps-5">
  Stepped animation (5 steps)
</div>

Compiles to:

<motion.div
  transition={{ duration: 1, ease: "steps(5)" }}
/>
ClassCompiled valueResult
animate-ease-steps-3ease: "steps(3)"3 discrete steps
animate-ease-steps-5ease: "steps(5)"5 discrete steps
animate-ease-steps-10ease: "steps(10)"10 discrete steps

Higher step counts produce smoother-looking motion, while lower counts create a more visible staircase effect.


Duration Control

The animate-duration-{ms} class sets how long a tween animation takes, in milliseconds. The value is converted to seconds in the compiled output.

Duration Comparison
<div className="... animate-duration-200">200ms</div>
<div className="... animate-duration-500">500ms</div>
<div className="... animate-duration-1000">1000ms</div>
ClassCompiled value
animate-duration-200duration: 0.2
animate-duration-500duration: 0.5
animate-duration-1000duration: 1

When using springs, setting a duration switches the spring to a duration-based spring that takes exactly the specified time to complete, losing the natural overshoot behavior.


Delay

The animate-delay-{ms} class postpones the start of the animation. The value is in milliseconds and converts to seconds.

Delayed Animation
<div className="animate-initial:opacity-0 animate-initial:y-20 animate-enter:opacity-100 animate-enter:y-0 animate-duration-500 animate-delay-500">
  I wait 500ms before animating
</div>

Compiles to:

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.5, delay: 0.5 }}
/>

Delay works with both tween and spring animations. It is also commonly used for staggering multiple elements (see Enter & Exit Animations).


Combining Transition Options

All transition classes can be freely combined. They merge into a single transition object in the compiled output.

Combined: Spring + Delay
<div className="animate-initial:opacity-0 animate-initial:scale-50 animate-initial:rotate-12 animate-enter:opacity-100 animate-enter:scale-100 animate-enter:rotate-0 animate-spring animate-stiffness-300 animate-damping-12 animate-delay-200">
  Spring + delay + rotate
</div>

Compiles to:

<motion.div
  initial={{ opacity: 0, scale: 0.5, rotate: 12 }}
  animate={{ opacity: 1, scale: 1, rotate: 0 }}
  transition={{ type: "spring", stiffness: 300, damping: 12, delay: 0.2 }}
/>

Repeat Type

Control how an animation behaves when it repeats. By default, repeating animations restart from the beginning each cycle. Use repeat type classes to change this behavior.

ClassCompiled valueBehavior
animate-repeat-reverserepeatType: "reverse"Alternates direction each cycle
animate-repeat-mirrorrepeatType: "mirror"Same as reverse -- alternates direction
Repeat Reverse
<div className="animate-initial:x-0 animate-enter:x-100 animate-repeat-infinite animate-repeat-reverse animate-duration-1000">
  Back and forth
</div>

Compiles to:

<motion.div
  initial={{ x: 0 }}
  animate={{ x: 100 }}
  transition={{ duration: 1, repeat: Infinity, repeatType: "reverse" }}
/>

Without animate-repeat-reverse, the element would snap back to the start position at the end of each cycle. With it, the animation smoothly reverses direction, creating a ping-pong effect.


Repeat Delay

The animate-repeat-delay-{ms} class adds a pause between each repetition of the animation. The value is in milliseconds and converts to seconds.

<div className="animate-initial:opacity-50 animate-enter:opacity-100 animate-repeat-infinite animate-repeat-reverse animate-repeat-delay-500 animate-duration-800">
  Pulse with pause
</div>

Compiles to:

<motion.div
  initial={{ opacity: 0.5 }}
  animate={{ opacity: 1 }}
  transition={{ duration: 0.8, repeat: Infinity, repeatType: "reverse", repeatDelay: 0.5 }}
/>
ClassCompiled value
animate-repeat-delay-200repeatDelay: 0.2
animate-repeat-delay-500repeatDelay: 0.5
animate-repeat-delay-1000repeatDelay: 1

The delay occurs after each cycle completes and before the next cycle begins. This is useful for creating pulsing or breathing effects with a natural pause.


Stagger Children

The animate-stagger-{ms} class is applied to a parent element to stagger the start of each child's animation. The value is in milliseconds and converts to seconds. This creates a cascading effect where each child begins its animation slightly after the previous one.

Staggered Children
<div className="animate-stagger-100">
  <div className="animate-initial:opacity-0 animate-initial:y-20 animate-enter:opacity-100 animate-enter:y-0 animate-duration-400">First</div>
  <div className="animate-initial:opacity-0 animate-initial:y-20 animate-enter:opacity-100 animate-enter:y-0 animate-duration-400">Second</div>
  <div className="animate-initial:opacity-0 animate-initial:y-20 animate-enter:opacity-100 animate-enter:y-0 animate-duration-400">Third</div>
</div>

Compiles to:

<motion.div transition={{ staggerChildren: 0.1 }}>
  <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4 }} />
  <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4 }} />
  <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4 }} />
</motion.div>
ClassCompiled value
animate-stagger-50staggerChildren: 0.05
animate-stagger-100staggerChildren: 0.1
animate-stagger-200staggerChildren: 0.2

Reverse Stagger Order

Use animate-stagger-reverse to reverse the stagger direction, so the last child animates first.

<div className="animate-stagger-100 animate-stagger-reverse">
  <div className="...">Animates third</div>
  <div className="...">Animates second</div>
  <div className="...">Animates first</div>
</div>

Compiles to:

<motion.div transition={{ staggerChildren: 0.1, staggerDirection: -1 }}>
  ...
</motion.div>

Delay Children

The animate-delay-children-{ms} class delays all children animations by the specified amount. This is applied to the parent element and affects every child uniformly, unlike stagger which adds incremental delay. The value is in milliseconds and converts to seconds.

<div className="animate-delay-children-200 animate-stagger-100">
  <div className="animate-initial:opacity-0 animate-enter:opacity-100">First (starts at 200ms)</div>
  <div className="animate-initial:opacity-0 animate-enter:opacity-100">Second (starts at 300ms)</div>
  <div className="animate-initial:opacity-0 animate-enter:opacity-100">Third (starts at 400ms)</div>
</div>

Compiles to:

<motion.div transition={{ delayChildren: 0.2, staggerChildren: 0.1 }}>
  <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
  <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
  <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
</motion.div>
ClassCompiled value
animate-delay-children-100delayChildren: 0.1
animate-delay-children-200delayChildren: 0.2
animate-delay-children-500delayChildren: 0.5

When combined with animate-stagger-{ms}, the delay children value is applied first, then stagger is added incrementally on top. In the example above, the first child starts at 200ms, the second at 300ms, and the third at 400ms.


Orchestration (when)

Orchestration classes control the order in which a parent and its children animate. By default, parent and children animations run simultaneously. Use animate-when-before or animate-when-after to sequence them.

ClassCompiled valueBehavior
animate-when-beforewhen: "beforeChildren"Parent animates first, then children start
animate-when-afterwhen: "afterChildren"Children animate first, then parent starts
<div className="animate-initial:opacity-0 animate-enter:opacity-100 animate-when-before animate-stagger-100">
  <div className="animate-initial:y-20 animate-enter:y-0">Child 1</div>
  <div className="animate-initial:y-20 animate-enter:y-0">Child 2</div>
  <div className="animate-initial:y-20 animate-enter:y-0">Child 3</div>
</div>

Compiles to:

<motion.div
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  transition={{ when: "beforeChildren", staggerChildren: 0.1 }}
>
  <motion.div initial={{ y: 20 }} animate={{ y: 0 }} />
  <motion.div initial={{ y: 20 }} animate={{ y: 0 }} />
  <motion.div initial={{ y: 20 }} animate={{ y: 0 }} />
</motion.div>

In this example, the parent fades in first. Once the parent animation completes, the children slide up one by one with a 100ms stagger between them.


Rest Speed & Rest Delta

These classes fine-tune when a spring animation is considered "at rest" and stops calculating. They only apply to spring animations.

ClassCompiled valueControls
animate-rest-speed-{n}restSpeed: nVelocity threshold below which the spring is considered at rest
animate-rest-delta-{n}restDelta: nDistance from target below which the spring is considered at rest
<div className="animate-initial:x-0 animate-enter:x-200 animate-spring animate-stiffness-300 animate-damping-10 animate-rest-speed-2 animate-rest-delta-1">
  Faster settling
</div>

Compiles to:

<motion.div
  initial={{ x: 0 }}
  animate={{ x: 200 }}
  transition={{ type: "spring", stiffness: 300, damping: 10, restSpeed: 2, restDelta: 1 }}
/>

Increasing restSpeed and restDelta causes the spring to stop sooner, cutting off the tail end of tiny oscillations. This is useful for performance or when you want the animation to feel "done" faster. Lowering these values makes the spring more precise but can result in longer settling times.


Keyframe Times

The animate-times-[...] class controls when each keyframe is reached during a keyframe animation. Values are provided as a comma-separated list in the range 0 to 1, where 0 is the start and 1 is the end of the animation.

<div className="animate-initial:opacity-0 animate-enter:opacity-100 animate-duration-1000 animate-times-[0,0.5,1]">
  Timed keyframes
</div>

Compiles to:

<motion.div
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  transition={{ duration: 1, times: [0, 0.5, 1] }}
/>
ClassCompiled value
animate-times-[0,0.5,1]times: [0, 0.5, 1]
animate-times-[0,0.2,0.8,1]times: [0, 0.2, 0.8, 1]
animate-times-[0,0.1,0.9,1]times: [0, 0.1, 0.9, 1]

The number of time values should match the number of keyframes in the animation. Each time value maps to the corresponding keyframe, giving you precise control over the pacing of multi-step animations. For example, [0, 0.2, 0.8, 1] means the second keyframe is reached at 20% of the duration, and the third at 80%.


Inertia Animations

Inertia is a Motion animation type designed for decelerating a value based on its initial velocity. It is primarily used internally by Motion's drag system -- when you release a dragged element, inertia determines how it decelerates to a stop.

This is not directly exposed through motionwind classes. Inertia animations are triggered automatically by Motion when drag ends, or can be used manually via the useAnimate hook:

import { useAnimate } from "motion/react";

function InertiaExample() {
  const [scope, animate] = useAnimate();

  const handleDragEnd = (event, info) => {
    animate(scope.current, { x: 0 }, {
      type: "inertia",
      velocity: info.velocity.x,
    });
  };

  return (
    <div
      ref={scope}
      className="animate-drag-x animate-drag-elastic-50"
    />
  );
}

Motionwind's animate-drag-* classes handle the drag configuration, but the post-drag deceleration behavior is controlled by Motion's internal inertia system.


Velocity-Based Animations

Motion's spring animations are inherently velocity-aware. When animating from one value to another, the spring system considers the current velocity of the value, producing smooth transitions even when an animation is interrupted mid-flight.

You do not need to configure this. Motion handles velocity continuity automatically when using springs. If you interrupt a spring animation by changing the target value, the spring picks up from the current velocity rather than starting from rest.

<!-- The spring automatically handles velocity when retargeted -->
<div className="animate-hover:scale-110 animate-tap:scale-95 animate-spring animate-stiffness-400 animate-damping-15">
  Hover then quickly tap -- the spring transition is seamless
</div>

This velocity continuity is one of the key advantages of spring animations over tween animations, and it works automatically with all motionwind spring classes.


Quick Reference

ClassCompiles toNotes
animate-springtype: "spring"Enables physics-based animation
animate-stiffness-{n}stiffness: nSpring tension (direct value)
animate-damping-{n}damping: nSpring resistance (direct value)
animate-mass-{n}mass: n / 10Object weight (divided by 10)
animate-bounce-{n}bounce: n / 100Bounciness shorthand (divided by 100)
animate-ease-inease: "easeIn"Tween: slow start
animate-ease-outease: "easeOut"Tween: slow end
animate-ease-in-outease: "easeInOut"Tween: slow both ends
animate-ease-linearease: "linear"Tween: constant speed
animate-ease-circ-inease: "circIn"Circular ease in (sharper curve)
animate-ease-circ-outease: "circOut"Circular ease out
animate-ease-circ-in-outease: "circInOut"Circular ease in-out
animate-ease-back-inease: "backIn"Enters with overshoot
animate-ease-back-outease: "backOut"Exits with anticipation
animate-ease-back-in-outease: "backInOut"Both overshoot and anticipation
animate-ease-anticipateease: "anticipate"Pulls back then shoots forward
animate-ease-[n,n,n,n]ease: [n, n, n, n]Custom cubic-bezier curve
animate-ease-steps-{n}ease: "steps(n)"Stepped animation with n steps
animate-duration-{ms}duration: ms / 1000Animation length in ms
animate-delay-{ms}delay: ms / 1000Start delay in ms
animate-repeat-reverserepeatType: "reverse"Alternates direction each cycle
animate-repeat-mirrorrepeatType: "mirror"Same as reverse
animate-repeat-delay-{ms}repeatDelay: ms / 1000Pause between repeats
animate-stagger-{ms}staggerChildren: ms / 1000Stagger child animations (on parent)
animate-stagger-reversestaggerDirection: -1Reverse stagger order
animate-delay-children-{ms}delayChildren: ms / 1000Delay all children animations
animate-when-beforewhen: "beforeChildren"Parent animates before children
animate-when-afterwhen: "afterChildren"Parent animates after children
animate-rest-speed-{n}restSpeed: nSpring rest velocity threshold
animate-rest-delta-{n}restDelta: nSpring rest distance threshold
animate-times-[...]times: [...]Keyframe timing control