Variants: Step-by-step tutorial

Matt Perry
In this tutorial, we're going to build the Variants example step-by-step.
This example is rated advanced difficulty, which means we assume you're already quite familiar with Motion (and JavaScript in general).
Here's a live demo of the example we're going to be creating:
Introduction
The variants example shows a common UI pattern: a hamburger menu that opens to reveal a navigation list. The animation is orchestrated, with the background, the list items, and the icon itself all animating in a coordinated sequence.
Managing this kind of complex animation manually would be difficult. This is where variants come in. In this tutorial, we'll learn how to use the variants
API to define animation states and automatically propagate them through a tree of motion components.
Getting started
Let's begin with the basic structure of the menu. This includes the MenuToggle
, Navigation
, and MenuItem
components, with the state to control the open/closed status. For now, it will be a functional but unanimated menu.
Animating with variants
Variants are visual states with names.
These states can be passed to a motion
component and then we can provide the names to animation props like initial
and animate
:
What makes variants so powerful is children can also define their own variants
:
And then these can be controlled via their parent:
The real superpower is that the parent can now orchestrate when children animations are performed. Child animations can be played before or after those of the parent. Or using delayChildren
after a set delay. This delay can also be staggered using the stagger()
function.
With that in mind, let's see this in action.
Step 1: Animating the menu items
Let's start from the inside out. We want each MenuItem
to fade in and slide up. We can define itemVariants
for this.
Now, we can convert our li
to a motion.li
and pass it these variants. We also add some whileHover
and whileTap
gestures to add some microinteraction animations.
Step 2: Orchestrating the list with stagger
We don't want the items to appear all at once. Instead, we want them to animate in one by one. We can achieve this on their parent, the Navigation
component, using the stagger
function.
The stagger
function is a powerful tool that generates a dynamic delay for the delayChildren
transition property, based on the child index.
For the
open
variant, we'll wait0.2
seconds before starting, then animate each child with a0.07s
delay.For the
closed
variant, we'll use{ from: "last" }
to stagger in reverse, making the items disappear from the bottom up.
Step 3: Animating the background
The background uses a different technique: a circular reveal created by animating a clip-path
.
The sidebarVariants
define a closed
state with a small circle around the menu icon, and an open
state with a circle large enough to cover the entire menu.
Notice the open
variant is a function: open: (height = 1000) => ({ ... })
. This is a dynamic variant. It can receive a value from the parent's custom
prop. We do this to pass in the measured height
of the menu, ensuring our circle is always big enough.
Step 4: Putting it all together
Finally, we assemble everything in our main component. The top-level motion.nav
component acts as the orchestra conductor.
animate={isOpen ? "open" : "closed"}
: This single prop controls everything. WhenisOpen
changes, it tells all children to switch to the new variant state.custom={height}
: This passes the measured height of the container down to any dynamic variants that need it (like oursidebarVariants
).
Here is the final top-level component:
Conclusion
In this tutorial, we learned how variants can simplify complex, orchestrated animations. By defining named animation states, we can control a whole tree of components with a single prop. We also saw how to use the stagger
function to create beautiful cascading sequences and how to pass data to animations using the custom
prop and dynamic variants.