Skip to main content

Live demo

Drag gestures enable users to directly manipulate elements by clicking and dragging them across the screen. With proper physics and constraints, drag interactions feel natural and intuitive.
Always provide visual feedback during drag operations and respect drag constraints to prevent elements from leaving their containers.

Complete code example

import { motion, useMotionValue, useTransform } from 'framer-motion'

export default function DragDemo() {
  const x = useMotionValue(0)
  const y = useMotionValue(0)
  const rotate = useTransform(x, [-100, 100], [-30, 30])
  const scale = useTransform(y, [-100, 100], [0.5, 3])

  return (
    <div className="h-96 relative bg-gray-50 rounded-lg flex items-center justify-center">
      <motion.div
        drag
        dragElastic={0.2}
        dragConstraints={{
          top: -100,
          left: -100,
          right: 100,
          bottom: 100
        }}
        style={{ x, y, rotate, scale }}
        whileDrag={{ boxShadow: '0 8px 32px rgba(0,0,0,0.2)' }}
        className="w-32 h-32 bg-gradient-to-br from-purple-500 to-pink-500 rounded-lg cursor-grab active:cursor-grabbing"
      />
      <p className="absolute bottom-4 text-sm text-gray-500">
        Drag the box around
      </p>
    </div>
  )
}

How it works

Drag gestures combine event handling with physics:
1

Enable drag

Add the drag prop to make an element draggable. Use drag="x" or drag="y" to constrain to one axis.
2

Set constraints

Define boundaries with dragConstraints to prevent elements from leaving designated areas.
3

Add elasticity

Use dragElastic to create a rubber-band effect when dragging beyond constraints.
4

Apply transforms

Link motion values to create dynamic effects like rotation or scaling during drag.

Key drag properties

drag

Enable dragging: true, "x", or "y"

dragConstraints

Boundaries: { top, left, right, bottom } or ref to container

dragElastic

Elasticity: 0 (none) to 1 (full bounce)

dragMomentum

Enable/disable momentum after release

Variations

Swipeable cards

Tinder-style card swiping:
import { motion, useMotionValue, useTransform } from 'framer-motion'
import { useState } from 'react'

function SwipeCard({ onSwipe }) {
  const x = useMotionValue(0)
  const rotate = useTransform(x, [-200, 200], [-50, 50])
  const opacity = useTransform(x, [-200, -100, 0, 100, 200], [0, 1, 1, 1, 0])

  return (
    <motion.div
      drag="x"
      dragConstraints={{ left: 0, right: 0 }}
      style={{ x, rotate, opacity }}
      onDragEnd={(e, { offset, velocity }) => {
        if (Math.abs(offset.x) > 100) {
          onSwipe(offset.x > 0 ? 'right' : 'left')
        }
      }}
      className="w-80 h-96 bg-white rounded-xl shadow-xl p-6"
    >
      Card Content
    </motion.div>
  )
}

Draggable slider

Custom range input:
import { motion, useMotionValue, useTransform } from 'framer-motion'
import { useEffect, useState } from 'react'

function DragSlider({ min = 0, max = 100, onChange }) {
  const x = useMotionValue(0)
  const value = useTransform(x, [0, 200], [min, max])
  const [displayValue, setDisplayValue] = useState(min)

  useEffect(() => {
    return value.onChange(v => {
      setDisplayValue(Math.round(v))
      onChange?.(Math.round(v))
    })
  }, [])

  return (
    <div className="relative w-64 h-12 flex items-center">
      <div className="absolute w-full h-1 bg-gray-200 rounded" />
      <motion.div
        drag="x"
        dragConstraints={{ left: 0, right: 200 }}
        dragElastic={0}
        style={{ x }}
        className="w-12 h-12 bg-blue-500 rounded-full cursor-grab active:cursor-grabbing flex items-center justify-center text-white font-bold"
      >
        {displayValue}
      </motion.div>
    </div>
  )
}

Drag to reorder list

Sortable list items:
import { Reorder } from 'framer-motion'
import { useState } from 'react'

function SortableList() {
  const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3', 'Item 4'])

  return (
    <Reorder.Group axis="y" values={items} onReorder={setItems}>
      {items.map((item) => (
        <Reorder.Item
          key={item}
          value={item}
          className="p-4 mb-2 bg-white rounded-lg shadow cursor-grab active:cursor-grabbing"
        >
          <div className="flex items-center gap-3">
            <svg className="w-5 h-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
              <path d="M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z" />
            </svg>
            {item}
          </div>
        </Reorder.Item>
      ))}
    </Reorder.Group>
  )
}

Example from the playground

From the ReactAnimations component:
import { motion, useMotionValue, useTransform } from 'framer-motion'

function DragGestureDemo() {
  const x = useMotionValue(0)
  const y = useMotionValue(0)
  const rotate = useTransform(x, [-100, 100], [-30, 30])
  const scale = useTransform(y, [-100, 100], [0.5, 3])

  return (
    <div className="h-96 relative bg-gray-50 rounded-lg flex items-center justify-center">
      <motion.div
        drag
        dragElastic={0.2}
        dragConstraints={{
          top: -100,
          left: -100,
          right: 100,
          bottom: 100
        }}
        style={{ x, y, rotate, scale }}
        className="w-32 h-32 bg-gradient-to-br from-purple-500 to-pink-500 rounded-lg cursor-grab active:cursor-grabbing"
        whileDrag={{ boxShadow: '0 8px 32px rgba(0,0,0,0.2)' }}
      />
    </div>
  )
}

Best practices

Use cursor changes, shadows, or scale to indicate draggable elements.
<motion.div
  drag
  whileDrag={{ scale: 1.1, boxShadow: '0 8px 32px rgba(0,0,0,0.2)' }}
  className="cursor-grab active:cursor-grabbing"
/>
Always constrain drag boundaries to prevent elements from leaving the viewport.
// Using container ref
const containerRef = useRef(null)
<motion.div drag dragConstraints={containerRef} />
Momentum can make dragging feel natural but may confuse drop zones. Disable when needed.
<motion.div drag dragMomentum={false} />
Framer Motion handles touch automatically, but ensure hit targets are large enough (44x44px minimum).

Common use cases

  • Swipeable cards (Tinder-style)
  • Drag-to-reorder lists
  • Custom sliders and range inputs
  • Movable widgets and panels
  • Drawing and annotation tools
  • Game controls
  • Image cropping tools
  • Kanban boards

Performance tips

Drag operations use motion values which are optimized for performance. Avoid reading motion values on every frame in render.
// Good - use onChange callback
const x = useMotionValue(0)
useEffect(() => {
  return x.onChange(latest => console.log(latest))
}, [])

// Avoid - reads on every render
const x = useMotionValue(0)
const currentX = x.get() // Don't do this in render

Accessibility considerations

  • Provide keyboard alternatives for drag operations
  • Announce drag state changes to screen readers
  • Ensure draggable elements have sufficient contrast
  • Add ARIA attributes for drag-and-drop zones
<motion.div
  drag
  role="button"
  aria-label="Draggable card"
  tabIndex={0}
  onKeyDown={(e) => {
    // Handle arrow keys for keyboard users
  }}
/>

Scroll animation

Scroll-triggered animations

Slide in

Directional animations

Scale

Size transformations

Build docs developers (and LLMs) love