Gestures
Gestures are a very important aspect of user interfaces, they are what makes the elements feel alive. The user performs an action and the interface responds appropriately.
Gestures in Motion
We interact with interfaces constantly, we hover over buttons, tap links and drag cards. Motion extends React’s basic event system with a simple set of gestures that make these interactions feel alive.
To use gestures in Motion we have to use the motion component. It supports hover, tap, pan, drag, focus, and inView gestures.
Each of these gestures can trigger animations through while- props or be handled manually with event listeners.
Hover
Hover is the most obvious gesture, but still worth mentioning. whileHover detects when a pointer enters or leaves the element.
The great part about Motion is that you can easily use spring animations for any of these gestures, which is pretty difficult in CSS.
<motion.div
whileHover={{ rotate: 180 }}
transition={{ type: "spring", duration: 0.8, bounce: 0 }}
/>
For simple effects like these, both CSS and Motion work well. I personally prefer Motion, because I am able to use springs, which create more natural looking motion.
Performance is worth keeping in mind, but for effects that involve transform, clipPath or filter CSS and Motion behave almost identically.
Even though Motion uses Javascript, these animations still run on the compositor thread, just like CSS. I covered this a bit in my will-change in CSS article.
In most cases, there’s no meaningful performance difference. Sometimes Motion's performance is even better, because it handles independent transforms more efficiently than CSS variables.
Tap
The whileTap prop fires when a pointer or a keyboard key presses and releases on the same element.
Tap automatically cancels if the pointer moves too far so it won’t conflict with drags.
<motion.div
whileTap={{ scale: 0.8 }}
transition={{ type: "spring", duration: 0.5, bounce: 0 }}
/>
It’s also keyboard accessible. Pressing Enter will trigger onTapStart and whileTap, releasing Enter fires onTap.
Drag
The drag gesture applies pointer movement directly to a component. Motion provides a lot of controls out of the box. To make a component draggable, you can simply add the drag prop to a motion component.
You can then add whileDrag and the property that you want to animate. In this example, I also added dragConstraints, so the element can't be dragged outside of its container.
<motion.div
whileDrag={{ scale: 0.8 }}
drag
dragConstraints={constraintsRef}
transition={{ type: "spring", duration: 0.5, bounce: 0 }}
/>
The drag gesture is extremely flexible, the Motion Docs cover many more use cases and configurations.
Pan
The pan gesture recognizes when a pointer moves more than 3 pixels while pressed and ends when it’s released.
Pan doesn't have an associated while- prop, so you'll need to use event handlers like onPan, onPanStart and onPanEnd.
<motion.div
onPanStart={() => setIsPanning(true)}
onPanEnd={() => setIsPanning(false)}
animate={{ scale: isPanning ? 0.9 : 1 }}
/>
Circling back to spring animations, a slider component like this is a place where it's definitely worth using spring animations in my opinion, as the motion here looks natural.
Focus
The whileFocus prop fires when an element gains or loses focus. It follows the same logic as :focus-visible in CSS.
This adds subtle visual feedback for keyboard navigation or accessibility flows.
<motion.button
whileFocus={{ scale: 1.1 }}
transition={{ type: "spring", duration: 0.3, bounce: 0 }}
/>
It is great for elements like buttons, input fields and others that you want to have a clear visual response when they are focused.
Since you can animate almost any value, you can get very creative with implementing even more complex effects.
However, I'd recommend keeping focus animations subtle. Overly complex motion can feel distracting or even disorienting when navigating by keyboard.
In View
whileInView triggers when an element enters or exits the viewport.
It is great for scroll-based reveals or progressive section entrances. You can also pair it with the initial prop to make the component animate from and to a specific state.
<motion.div
initial={{ filter: "blur(8px)" }}
whileInView={{ scale: 1.1, rotate: 45, filter: "blur(0px)" }}
viewport={{ root: scrollRef, amount: 0.5 }}
transition={{ type: "spring", duration: 0.6, bounce: 0 }}
/>
Like in this example above, where the initial (unrevealed) value of the blur filter is 8px and once the component is in view, it animates to 0px. There are a lot of great examples and creative ways to use this in the Motion Docs.
Conclusion
A lot of the things covered here can be found in the Motion Documentation.
This article is by no means supposed to just recite things that are written in the documentation, but instead point out that this something that is available to do with Motion, show how I use it in my work and showcase examples of where I use them.
Huge thanks to Matt Perry for providing feedback on this article.
More
In case you have any questions reach me at jakub@kbo.sk, see more of my work on Twitter or subscribe to my newsletter.
Newsletter
I share stuff that I'm working on, new posts and resources here.