[FEATURE] Defining transitions between props
There's a bit of a blind spot in the API at the moment where if you take
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1, transition: { delay: 1 } }}
whileHover={{ scale: 2 }}
/>
For instance you can see that when we re-enter the animate state there'll be a delay applied. When really the delay naturally applies to the initial animation.
It'd be good if we could figure out a way to define a transition that applies just on the initial animation. Perhaps there's also value in being able to define specific transitions more generally, like whileHover -> animate?
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1, transition: { delay: 1 } }}
whileHover={{ scale: 2 }}
transition={{
initial -> animate: {},
whileHover -> animate: {}
}}
/>
Or
<motion.div
initial={{ scale: 0 }}
animate={{
scale: 1,
transitionFrom: {
initial: { delay: 1 },
whileHover: { type: "spring" }
}
}
whileHover={{
scale: 2
}}
/>
I've been thinking about this a bit today and wanted to share in case my thoughts aren't obvious.
Please take this with a grain of salt! I'm by no means an expert here, I'm certain you, Matt, know better than anyone. I also know maintaining OS is hard work. I can't even keep a single project afloat.
I just thought it might be fun to chime in and be a little part of some API design which I find fascinating.
Transitions as edges in a graph
To me, transitions are a phenomenon that inherently appears between states (variants), and therefore I believe it might make sense to model it likewise and declare transitions outside of variant declarations. Think of it like a finite state machine; a graph where states (variants) are nodes and transitions are the edges. In this sense it is arbitrary that the current implementation of transition implies an any variant -> current variant relationship.
In your first example, you provided pseudo code for such syntax, specifically:
...
transition={{
initial -> animate: {},
whileHover -> animate: {}
}}
...
Syntactic sugar for variants
Because I'm guessing it can become too verbose to declare the transitions in the previously described manner, I think it makes sense to add syntactic sugar to variants to handle the most frequent transition use cases.
You proposed such syntactic sugar in your second example, specifically:
...
animate={{
scale: 1,
transitionFrom: {
initial: { delay: 1 },
whileHover: { type: "spring" }
}
}
...
With transitionFrom, though, you might also want to add transitionTo. This would provide the following APIs:
transition: This prop currently handlesany variant -> current variationtransitions.transitionFrom: This prop would handlespecific variant -> current varianttransitions.transitionTo: This prop would handlecurrent variant -> specific varianttransitions.
But by the symmetry of these relationships, you might notice that there is no corresponding inverse of transition with the relation current variation -> any variant. As such you might introduce transitionToAny, whose name highlights the fact that transition has really been transitionFromAny in disguise all along.
That would leave us with:
transition: Alias oftransitionFromAnytransitionFrom:specific variant -> current varianttransitionFromAny:any variant -> current varianttransitionTo:current variant -> specific varianttransitionToAny:current variant -> any variant
I would personally just implement a * wildcard to represent any variant, though, and stick to:
transition: Alias oftransitionFromwith*wildcard.transitionFrom:specific variant -> current variantwherespecific variantcan be a*wildcard.transitionTo:current variant -> specific variantwherespecific variantcan be a*wildcard.
transitions syntax
Here I would like to note the few obvious syntaxes I've thought of declaring transitions separately from variants.
Verbose object notation
<motion.div
...
transitions={
[
{
from: 'variant1',
to: 'variant2',
transition: {
delay: 0.5,
},
},
{
from: 'variant2',
to: '*', // `*` acts as a wildcard for any variant.
transition: {
delay: 1,
},
},
{
// Not specifying `from` implicitly sets it to `*`
to: 'variant3',
transition: {
delay: 2,
},
},
{
from: 'variant4',
// Not specifying `to` implicitly sets it to `*`
transition: {
delay: 4,
},
},
],
}
/>
Easy to read and understand, but verbose.
As shown, specifying only one of from and to implies the other is a wildcard.
Concise object notation using arrays
<motion.div
...
transitions={[
['variant1', 'variant2', {
delay: 0.5,
}],
['variant2', '*', {
delay: 1,
}],
['*', 'variant3', {
delay: 2,
}],
]}
/>;
Short and fairly easy to read. Unfortunately, Prettier will mess this one up big time if variations aren't written as one-liners. 🙁
You could choose to allow specifying one variant only, and imply it as from or to, but I'd vote against it for readability reasons.
A useful helper function
No matter the syntax, a simple helper function would clean it up a lot:
const makeTransition = (from, to, transition) => ({ from, to, transition });
const MyComponent = () => {
return (
<motion.div
...
transitions={[
makeTransition("hidden", "visible", { duration: 1 }),
makeTransition("visible", "hidden", {
duration: 2,
delay: 10,
ease: "easeInOut",
}),
]}
/>
);
};
This helper function plays nicely with Prettier and also makes the code very readable if you are using VS Code's Inlay Hints that show the names of parameters. In any case it will show in the verbosity the user has set in their preferences.

I assume you wouldn't want to surface such a helper function from the API, though. Maybe it could be added to the docs.
Precedence
It would be necessary to take a look at precedence in these syntaxes.
Wildcard vs. non-wildcard
Non-wildcard relations should likely always override wildcard relations.
Eg.: variant1 -> variant2 would take precedence over variant1 -> * and * -> variant2
Wildcard vs. wildcard
A transition that is specified both via a wildcard at the source and via a wildcard at the destination could take into account both the position of the transition in a transitions array and whether the wildcard is on the source or the destination.
Eg.:
- Given a transitions array with the relations
variant1 -> *and* -> variant2,variant1 -> *might be chosen as it is specified first. - Given a transitions array with the relations
variant1 -> *and* -> variant2,* -> variant2might be chosen as it is specified last. - Given a transitions array with the relations
variant1 -> *and* -> variant2,variant1 -> *might be chosen as a variant is specified for the source. - Given a transitions array with the relations
variant1 -> *and* -> variant2,* -> variant2might be chosen as a variant is specified for the destination. This is probably better than the former, as this aligns better with the current relationship oftransitionas mentioned earlier,any variant -> current variant.
Finally
Again, please take this with a grain of salt and have a great day 😊
Amazing post! Thanks for help thinking this through.
On transition, transitionFrom etc... I think just these two would be enough. transitionTo and transitionFrom creates a potential conflict and the current paradigm is that the variant being entered is the one that defines the transition used. This is similar to CSS. transitionFrom would be enough to then further designate a specific transition to use for this variant combination.
Ideally we'd add as little API as possible to achieve our aims and I think just this would be enough, vs transitions which could itself get quite verbose.
There's also the added wrinkle of prop names/gestures vs variants.
Prop names, useful with or without variants
<motion.div
animate={{
scale: 1,
transitionFrom: {
whileHover: { duration: 1 }
}
}}
whileHover={{
scale: 2
}}
/>
Or from variant names, only useful with variants
<motion.div
variants={{
enter: /** **/,
exit: {
transitionFrom: {
enter: { duration: 1 }
}
}
}}
whileHover={{
scale: 2
}}
/>
Supporting both might be quite difficult though I'll take a look into this as I can see the use of both.
Makes total sense to try to add as small changes to the API as possible, and you're totally correct it gets more complicated than I outlined when opening the whole transitions can of worms.
transitionFrom would be enough to cover our needs, and I assume other people's.
Would be wonderful to see transitionFrom implemented 👏
Hey @mattgperry, just hit this problem again, and saw you have been working on this, but closed #2332 a couple weeks ago.
Did you find a blocker that prevents us from implementing transitionForm?
Let me know if not and you think I should give it a shot.
Edit: Is it revived here? #2643
Hey @akd-io
This is the closest as I think I'll implement https://github.com/motiondivision/motion/pull/2951
#2951 Looks great 👌 Gave it a heart a long time ago 😊 I'm confident you know better than me what's best for the library.
@FanManutd Didn't understand your message, did you need something from me? 😊