Skip to main content
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>

Performance Tips

  • 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

Build docs developers (and LLMs) love