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.div
tabIndex={0}
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.
Stay In The Loop
Get updates when I share new posts, resources and tips.