motion-solid

Transitions

A transition describes how a value moves to its target: with a duration and an easing curve, with spring physics, or by decelerating from a velocity.

Setting a transition

The transition prop applies to every animation of the component:

<motion.div
    animate={{ x: isOpen() ? 200 : 0 }}
    transition={{ duration: 0.4, ease: "easeOut" }}
/>

A target can carry its own transition, which wins over the prop. Use it to animate differently depending on the state, or for a gesture:

<motion.div
    animate={{ opacity: 1, transition: { duration: 1 } }}
    whileHover={{ scale: 1.1, transition: { duration: 0.15 } }}
    exit={{ opacity: 0, transition: { duration: 0.2 } }}
/>

The same goes for variants: a variant's transition is used when animating to it.

Defaults

Without a transition, Motion picks one per value:

  • x, y, rotate and the other transforms use a spring. scale uses a spring without bounce.
  • Other values, like opacity and colors, use a 0.3 second tween.
  • Animations with more than two keyframes use a 0.8 second tween.

A transition with only orchestration options (delay, repeat, when, ...) keeps these defaults.

Tween

A tween animates over a fixed duration (in seconds), shaped by an easing curve. It's the type used when you set duration or ease without a type. You can also set type: "tween".

OptionTypeDefaultDescription
durationnumber0.3Length of the animation, in seconds. 0.8 for more than two keyframes.
easeEasing | Easing[]"easeOut"The easing curve. An array sets one curve per keyframe segment.
timesnumber[]Where each keyframe falls, from 0 to 1. See keyframes.

Easing

ease accepts:

  • A name: "linear", "easeIn", "easeOut", "easeInOut", "circIn", "circOut", "circInOut", "backIn", "backOut", "backInOut" or "anticipate".
  • A cubic bezier curve as an array of four numbers, like in CSS: [0.7, 0, 0.3, 1].
  • A function that takes a progress from 0 to 1 and returns the eased progress. The easing functions are exported too (easeOut, backOut, steps(4), cubicBezier(…), ...).
linear
easeIn
easeOut
easeInOut
backOut
[0.7, 0, 0.3, 1]

Spring

type: "spring" moves the value like a spring. Springs keep the value's velocity when they're interrupted, so a new target redirects the movement instead of restarting it. That makes them a good fit for gestures and for values that change often.

There are two ways to configure a spring. With visualDuration (or duration) and bounce, you describe how it looks:

<motion.div
    animate={{ x: 200 }}
    transition={{ type: "spring", visualDuration: 0.4, bounce: 0.25 }}
/>

Set bounce together with visualDuration: on its own, visualDuration is ignored and the spring uses the default physics.

With stiffness, damping and mass, you describe the physics. These override duration and bounce. Without any of these options, a spring uses stiffness: 100, damping: 10 and mass: 1.

bounce: 0
bounce: 0.3
bounce: 0.6
stiffness: 50, damping: 5
stiffness: 400, damping: 30
OptionTypeDefaultDescription
visualDurationnumberThe time (in seconds) the spring takes to visually reach its target. Bouncing continues after it. Overrides duration. Takes effect together with bounce.
durationnumberThe total time of the animation, in seconds.
bouncenumberFrom 0 (no bounce) to 1 (very bouncy). Negative values down to -1 start faster and settle more slowly. With duration alone, it's 0.3.
stiffnessnumber100Higher values move more suddenly.
dampingnumber10The force against the movement. 0 oscillates forever.
massnumber1Higher values move more slowly.
velocitynumbercurrentThe starting velocity. By default the value's current velocity.
restSpeednumberThe animation ends when the speed (units per second) is below this, and the distance below restDelta.
restDeltanumberThe animation ends when the distance to the target is below this, and the speed below restSpeed.

Inertia

type: "inertia" decelerates a value from its velocity, like something thrown that slows down. It's what drag uses when you release an element, and what dragTransition configures. It doesn't animate to a target: the velocity and power decide where the value comes to rest.

OptionTypeDefaultDescription
velocitynumbercurrentThe starting velocity.
powernumber0.8Higher values travel further.
timeConstantnumber700How long the deceleration takes, in milliseconds.
modifyTarget(target: number) => numberReceives the calculated resting point and returns a new one, for example to snap to a grid.
min, maxnumberBounds. The value springs back when it would cross them.
bounceStiffnessnumber500Stiffness of the spring at the bounds.
bounceDampingnumber10Damping of the spring at the bounds.

No animation

type: false sets the values right away, without animating. duration: 0 does the same for a tween.

Delay

delay waits this many seconds before the animation starts:

<motion.div animate={{ opacity: 1 }} transition={{ delay: 0.5 }} />

Repeat

OptionTypeDefaultDescription
repeatnumber0How many times to repeat. Infinity repeats forever.
repeatType"loop" | "reverse" | "mirror""loop"How each repetition plays.
repeatDelaynumber0Seconds to wait between repetitions.
  • "loop" starts again from the first keyframe.
  • "reverse" plays backwards, then forwards again. The easing is reversed too.
  • "mirror" swaps the start and the end, and plays forwards with the same easing.

The difference between "reverse" and "mirror" shows with an asymmetric easing like easeIn:

loop
reverse
mirror

Per-value transitions

A transition can set options for single values by their name. Values without their own options use the rest of the transition:

<motion.div
    animate={{ x: 100, opacity: 1 }}
    transition={{
        duration: 0.5,
        opacity: { duration: 0.2, ease: "linear" },
    }}
/>

default sets the transition of every value without its own options:

transition={{
    default: { type: "spring", bounce: 0.3 },
    opacity: { duration: 0.2 },
}}

A value's options replace the general ones; only delay carries over. Add inherit: true to a value's options to merge them with the general ones instead.

Inheriting transitions

MotionConfig sets a default transition for every motion component inside it:

import { MotionConfig, motion } from "@motion-s/solid"

<MotionConfig
    transition={{ type: "spring", visualDuration: 0.3, bounce: 0.2 }}
>
    <motion.div animate={{ x: 100 }} />
</MotionConfig>

A component's own transition prop replaces the one from MotionConfig. A nested MotionConfig replaces its parent's, unless its transition has inherit: true: then the two are merged, and its own options win.

In the same way, a transition inside a target or a variant replaces the component's transition prop, unless it has inherit: true:

<motion.div
    transition={{ type: "spring", bounce: 0.4 }}
    whileHover={{
        scale: 1.1,
        transition: { inherit: true, visualDuration: 0.15 },
    }}
/>

Staggering children

When a parent animates a variant, the orchestration options of its transition time its motion children (see variants). delayChildren delays them. With the stagger() helper, each child gets its own delay:

import { stagger } from "@motion-s/solid"

transition={{ delayChildren: stagger(0.05, { from: "center" }) }}

stagger(interval, options) adds interval seconds between children:

OptionTypeDefaultDescription
startDelaynumber0A delay before the first child, in seconds.
from"first" | "center" | "last" | number"first"The child that starts first. The others follow by their distance to it.
easeEasingDistributes the delays along an easing curve instead of evenly.

The older options staggerChildren (seconds between children) and staggerDirection (1 from the first child, -1 from the last) still work.

The transition of the parent is read when the animation starts, so it can depend on signals like any other prop, as from() does in the demo.