Motion’s gesture system makes it easy to create interactive animations that respond to user input. Gestures work seamlessly with variants and provide a simple, declarative API.
Overview
Motion components support several gesture types:
Hover : Mouse enter/leave events
Tap/Press : Click and touch interactions
Drag : Draggable elements with constraints
Pan : Pointer tracking without dragging
Focus : Keyboard focus states
Hover
Respond to mouse hover with whileHover:
import { motion } from "framer-motion"
function Button () {
return (
< motion.button
whileHover = { { scale: 1.1 } }
style = { { width: 100 , height: 100 , background: "white" } }
>
Hover me
</ motion.button >
)
}
Hover Events
Track hover state with callbacks:
< motion.div
whileHover = { { opacity: 0.5 } }
onHoverStart = { () => console . log ( "Hover started" ) }
onHoverEnd = { () => console . log ( "Hover ended" ) }
style = { { width: 100 , height: 100 , background: "white" } }
/>
Hover with Spring Transitions
< motion.div
whileHover = { { opacity: 0.5 } }
transition = { {
type: "spring" ,
stiffness: 300 ,
damping: 10
} }
style = { { width: 100 , height: 100 , background: "white" } }
/>
Tap
Animate on press/click with whileTap:
< motion.button
whileTap = { { scale: 0.95 } }
onTap = { () => console . log ( "Tapped" ) }
>
Click me
</ motion.button >
Tap Events
Motion provides three tap event callbacks:
< motion.div
whileTap = { { scale: 0.95 } }
onTap = { () => console . log ( "Tap completed" ) }
onTapStart = { () => console . log ( "Tap started" ) }
onTapCancel = { () => console . log ( "Tap cancelled" ) }
/>
onTap: Fires when tap completes successfully
onTapStart: Fires when pointer goes down
onTapCancel: Fires when tap is cancelled (pointer moves too far)
Tap with Variants
const buttonVariants = {
rest: { scale: 1 },
pressed: { scale: 0.95 }
}
< motion.button
variants = { buttonVariants }
initial = "rest"
whileTap = "pressed"
>
Press me
</ motion.button >
Global Tap Target
By default, tap ends when the pointer leaves the element. Use globalTapTarget to allow tapping anywhere:
< motion.button
whileTap = { { scale: 0.95 } }
globalTapTarget = { true }
>
Drag me away while pressing
</ motion.button >
Drag
Make elements draggable with the drag prop:
< motion.div
drag
style = { { width: 100 , height: 100 , background: "white" } }
/>
Constrain Drag Direction
Limit dragging to a single axis:
< motion.div drag = "x" /> // Horizontal only
< motion.div drag = "y" /> // Vertical only
Drag Constraints
Constrain drag to a specific area:
< motion.div
drag
dragConstraints = { { left: 0 , right: 100 , top: 0 , bottom: 100 } }
/>
Or use a ref for container-based constraints:
function Component () {
const constraintsRef = useRef ( null )
return (
< div ref = { constraintsRef } style = { { width: 400 , height: 400 } " >
< motion.div
drag
dragConstraints = { constraintsRef }
style = { { width: 100 , height: 100 , background: "white" } }
/>
</ div >
)
}
Drag Events
< motion.div
drag
onDragStart = { () => console . log ( "Drag started" ) }
onDrag = { () => console . log ( "Dragging" ) }
onDragEnd = { () => console . log ( "Drag ended" ) }
/>
Event handlers receive the event and drag info:
< motion.div
drag
onDrag = { ( event , info ) => {
console . log ( "Offset:" , info . offset . x , info . offset . y )
console . log ( "Velocity:" , info . velocity . x , info . velocity . y )
} }
/>
Drag Elasticity
Control drag resistance outside constraints:
< motion.div
drag
dragConstraints = { { left: 0 , right: 100 } }
dragElastic = { 0.2 } // 0 = no elasticity, 1 = full elasticity
/>
Drag Momentum
Disable momentum/inertia after dragging:
< motion.div
drag
dragMomentum = { false }
/>
Drag Controls
For advanced use cases, control drag programmatically with useDragControls:
import { motion , useDragControls } from "framer-motion"
function Component () {
const dragControls = useDragControls ()
return (
<>
< div
style = { {
width: 200 ,
height: 200 ,
background: "rgba(255,255,255,0.5)"
} }
onPointerDown = { ( e ) => dragControls . start ( e ) }
/>
< motion.div
drag
dragControls = { dragControls }
style = { { width: 100 , height: 100 , background: "white" } }
/>
</>
)
}
This allows dragging from a separate element (like a handle).
Pan
Track pointer movement without dragging:
< motion.div
onPan = { ( event , info ) => {
console . log ( "Pan:" , info . offset . x , info . offset . y )
} }
onPanStart = { ( event , info ) => console . log ( "Pan started" ) }
onPanEnd = { ( event , info ) => console . log ( "Pan ended" ) }
/>
Pan events fire when the pointer moves while pressed, providing:
offset: Total distance from start
delta: Distance since last event
velocity: Current velocity
Focus
Animate on keyboard focus:
< motion.input
whileFocus = { { scale: 1.05 } }
style = { { padding: 10 , fontSize: 16 } }
/>
Focus Events
< motion.input
onFocus = { () => console . log ( "Focused" ) }
onBlur = { () => console . log ( "Blurred" ) }
/>
Combining Gestures
All gestures work together:
import { motion } from "framer-motion"
import { useRef , useState } from "react"
function InteractiveBox () {
const ref = useRef ( null )
const [ isTap , setTap ] = useState ( false )
const [ isDrag , setDrag ] = useState ( false )
const [ isHover , setHover ] = useState ( false )
return (
< motion.div
drag
dragConstraints = { { left: 0 , right: 100 , top: 0 , bottom: 100 } }
dragElastic = { 0 }
whileTap = { { scale: 0.95 } }
onTap = { () => setTap ( false ) }
onTapStart = { () => setTap ( true ) }
onTapCancel = { () => setTap ( false ) }
onDragStart = { () => setDrag ( true ) }
onDragEnd = { () => setDrag ( false ) }
onHoverStart = { () => setHover ( true ) }
onHoverEnd = { () => setHover ( false ) }
style = { {
width: 200 ,
height: 200 ,
background: "white" ,
borderRadius: 20
} }
/>
)
}
Gesture Variants
Gestures work seamlessly with variants:
const cardVariants = {
rest: {
scale: 1 ,
boxShadow: "0px 2px 4px rgba(0,0,0,0.1)"
},
hover: {
scale: 1.05 ,
boxShadow: "0px 8px 16px rgba(0,0,0,0.2)"
},
pressed: {
scale: 0.95 ,
boxShadow: "0px 1px 2px rgba(0,0,0,0.1)"
}
}
< motion.div
variants = { cardVariants }
initial = "rest"
whileHover = "hover"
whileTap = "pressed"
/>
Propagation
Control event propagation:
< motion.div
onTap = { () => console . log ( "Parent clicked" ) }
>
< motion.button
onTap = { ( e ) => {
e . stopPropagation ()
console . log ( "Button clicked" )
} }
>
Click
</ motion.button >
</ motion.div >
Or use Motion’s propagation control:
< motion.button
propagate = { { tap: false } } // Prevents tap from bubbling
/>
Disabled State
Gestures respect disabled state on buttons:
< motion.button
whileTap = { { scale: 0.95 } }
disabled = { true } // whileTap won't trigger
>
Disabled
</ motion.button >
Drag uses MotionValues internally for optimal performance
Transform properties (x, y, scale, rotate) are GPU-accelerated
Use dragElastic={0} to disable elastic calculations if not needed
Avoid heavy calculations in gesture event handlers
Accessibility
Gesture animations should respect user preferences:
import { useReducedMotion } from "framer-motion"
function Component () {
const shouldReduceMotion = useReducedMotion ()
return (
< motion.button
whileHover = { shouldReduceMotion ? {} : { scale: 1.1 } }
whileTap = { shouldReduceMotion ? {} : { scale: 0.95 } }
>
Click me
</ motion.button >
)
}
Motion automatically respects prefers-reduced-motion when using transition configurations, but gesture animations may need manual handling.
API Reference
For complete API details, see:
Next Steps
Drag Guide Advanced drag interactions and examples
Motion Values Use MotionValues with gestures