Gesture Animations: Hover, Tap, and Click Interactions
Framer Motion includes built-in gesture detection that triggers animations on user interaction. Motion components listen for onHoverStart, onHoverEnd, onTap, and whileHover/whileTap props, letting you add instant visual feedback without manual event handling. A button that scales and changes color on hover, a card that lifts on tap—these are gesture animations, and they make interfaces feel responsive and alive.
I've added gesture animations to hundreds of interactive elements, from buttons to dashboard cards, and they're the fastest way to add perceived responsiveness to a UI.
Built-In Gesture Props
Framer Motion motion components accept gesture-specific animation props that trigger instantly on user input:
| Prop | Trigger | Use Case |
|---|---|---|
whileHover | Mouse enters element | Hover feedback (buttons, links) |
whileTap | Mouse down or touch | Click/tap feedback (buttons, cards) |
whileFocus | Element receives focus | Keyboard accessibility |
whileDrag | Element is dragged (covered next article) | Drag feedback |
These props accept the same animation object (opacity, scale, color, etc.) as animate, and Framer Motion automatically transitions to them on gesture.
Hover Animations with whileHover
The simplest gesture animation is hover feedback. When a user hovers over a button or card, scale it up, change the color, or add a shadow:
import { motion } from 'framer-motion';
export function HoverButton() {
return (
<motion.button
whileHover={{ scale: 1.05, boxShadow: '0 8px 16px rgba(0,0,0,0.2)' }}
whileTap={{ scale: 0.95 }}
style={{
padding: '12px 24px',
backgroundColor: '#3498db',
color: '#fff',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '16px',
fontWeight: 'bold',
boxShadow: '0 4px 6px rgba(0,0,0,0.1)'
}}
>
Hover Me
</motion.button>
);
}
The button grows slightly on hover (scale: 1.05) and a shadow deepens. On tap/click, it shrinks (scale: 0.95) to give tactile feedback. The animations happen automatically; no state management needed.
Tap Feedback with whileTap
whileTap triggers while the user is pressing down on a mouse or touch. This is perfect for buttons and clickable cards:
import { motion } from 'framer-motion';
export function TapCard() {
return (
<motion.div
whileHover={{
y: -8,
boxShadow: '0 12px 24px rgba(0,0,0,0.15)'
}}
whileTap={{ scale: 0.98 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
style={{
padding: '20px',
backgroundColor: '#fff',
borderRadius: '8px',
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
cursor: 'pointer',
userSelect: 'none'
}}
>
<h4>Interactive Card</h4>
<p>Lifts on hover, shrinks slightly on tap.</p>
</motion.div>
);
}
Hover lifts the card (y: -8 moves it up) and deepens the shadow. Tap shrinks it slightly (scale: 0.98). The spring transition makes the motion feel snappy and responsive. This pattern is used in product cards, list items, and dashboard tiles.
Chaining Hover and Tap Animations
You can combine whileHover and whileTap with transition to control the timing and easing:
import { motion } from 'framer-motion';
export function ChainedGestures() {
return (
<motion.button
whileHover={{
scale: 1.08,
backgroundColor: '#e74c3c'
}}
whileTap={{
scale: 0.92
}}
transition={{
duration: 0.2,
ease: 'easeInOut'
}}
style={{
padding: '14px 28px',
backgroundColor: '#e67e22',
color: '#fff',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '16px'
}}
>
Chain Gestures
</motion.button>
);
}
The hover and tap animations both use the specified transition (0.2s, easeInOut), so the motion is smooth and consistent.
Keyboard Focus Animations with whileFocus
For accessibility, also animate on keyboard focus. Screen reader users and keyboard navigators rely on visual feedback:
import { motion } from 'framer-motion';
export function AccessibleButton() {
return (
<motion.button
whileHover={{ scale: 1.05, backgroundColor: '#27ae60' }}
whileFocus={{
scale: 1.05,
backgroundColor: '#27ae60',
outline: '3px solid #fff',
outlineOffset: '2px'
}}
whileTap={{ scale: 0.95 }}
style={{
padding: '12px 24px',
backgroundColor: '#16a085',
color: '#fff',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '16px'
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
// Handle keyboard submit if needed
}
}}
>
Accessible Button
</motion.button>
);
}
The whileFocus animation mirrors whileHover with an added outline for visual clarity. This ensures keyboard users get the same feedback as mouse users.
Event Handlers vs Gesture Props
Gesture props are simpler than manual event handlers for most cases, but you can combine them:
import { motion } from 'framer-motion';
import React from 'react';
export function MixedGestureHandler() {
const [isActive, setIsActive] = React.useState(false);
return (
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={() => setIsActive(!isActive)}
animate={{ backgroundColor: isActive ? '#e74c3c' : '#3498db' }}
transition={{ backgroundColor: { duration: 0.3 } }}
style={{
padding: '12px 24px',
color: '#fff',
border: 'none',
borderRadius: '6px',
cursor: 'pointer'
}}
>
{isActive ? 'Active' : 'Inactive'}
</motion.button>
);
}
The button has:
whileHoverfor instant hover feedback (no state needed).whileTapfor instant tap feedback.onClickhandler to toggle state.animatetied to state for persistent color change.
This combines gesture animations with stateful animations.
Hover Animations on Lists and Grids
Apply gesture animations to list items or grid cards for a professional feel:
import { motion } from 'framer-motion';
export function HoverGrid() {
const items = Array.from({ length: 6 }, (_, i) => ({
id: i + 1,
title: `Item ${i + 1}`
}));
return (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))',
gap: '15px',
padding: '20px'
}}
>
{items.map((item) => (
<motion.div
key={item.id}
whileHover={{
y: -8,
boxShadow: '0 12px 24px rgba(0,0,0,0.2)'
}}
whileTap={{ scale: 0.95 }}
style={{
padding: '20px',
backgroundColor: '#f0f0f0',
borderRadius: '8px',
cursor: 'pointer',
boxShadow: '0 2px 8px rgba(0,0,0,0.08)'
}}
>
<h4>{item.title}</h4>
</motion.div>
))}
</div>
);
}
Each grid item lifts on hover and shrinks on tap. The gesture animations apply to every item automatically.
Preventing Gesture Animation Conflicts
If an element is already animated via animate, gesture props augment rather than replace it. To prevent conflicts, ensure animate and whileHover control different properties:
import { motion } from 'framer-motion';
import React from 'react';
export function ConflictPrevention() {
const [isLoading, setIsLoading] = React.useState(false);
return (
<motion.button
animate={{ opacity: isLoading ? 0.6 : 1 }}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
style={{
padding: '12px 24px',
backgroundColor: '#3498db',
color: '#fff',
border: 'none',
borderRadius: '6px',
cursor: 'pointer'
}}
onClick={() => setIsLoading(!isLoading)}
>
{isLoading ? 'Loading...' : 'Submit'}
</motion.button>
);
}
animate controls opacity (disabled state), while gesture props control scale. No conflict.
Key Takeaways
- Gesture props (
whileHover,whileTap,whileFocus) trigger animations instantly on user interaction. - No manual event handling needed for basic gesture feedback; Framer Motion detects and applies animations automatically.
- Combine gesture props with
transitionto control speed and easing. - Always include
whileFocusanimations for keyboard accessibility alongsidewhileHover. - Gesture props work with stateful animations—combine them for interactive, feedback-rich UIs.
Frequently Asked Questions
How do I disable gesture animations on certain elements?
Set whileHover or whileTap to an empty object {} or omit them entirely. On touch devices, whileHover won't trigger (only whileTap will), which is correct behavior.
Can I use whileHover and whileTap with non-motion elements?
Only with motion elements (motion.button, motion.div, etc.). For regular HTML, attach event listeners manually with onMouseEnter, onMouseLeave, onTouchStart, etc.
What's the difference between whileTap and onMouseDown?
whileTap is a Framer Motion gesture animation that applies while the user is pressing. onMouseDown is a React event handler that fires once. Use whileTap for visual feedback; use onMouseDown for logic.
Do gesture animations work on mobile touch devices?
Yes. whileHover doesn't trigger on touch (there's no hover on mobile), but whileTap and whileFocus work on touch and keyboard devices.