Skip to main content

Drag & Kinetic Motion: Velocity-Based Animation Handling

Drag animations in Framer Motion capture velocity when the user releases a dragged element, then apply inertia to let it coast to a stop naturally. A card swiped off-screen or a slider released mid-drag will continue moving in the direction of the swipe, decelerating smoothly. This kinetic motion (physics-based momentum) feels tactile and responsive, and it's what separates amateur UIs from polished ones.

I've implemented swipeable cards, draggable sliders, and kinetic scrolling components with Framer Motion's drag API, and the momentum behavior is what makes users say "wow, this feels good to use."

Basic Dragging with drag

Enable dragging on an element with the drag prop. Set it to "x" for horizontal, "y" for vertical, or true for both axes:

import { motion } from 'framer-motion';

export function DraggableBox() {
return (
<motion.div
drag
dragElastic={0.2}
dragTransition={{ power: 0.3, restDelta: 0.001 }}
style={{
width: '100px',
height: '100px',
backgroundColor: '#3498db',
borderRadius: '8px',
cursor: 'grab',
touchAction: 'none'
}}
>
Drag me
</motion.div>
);
}

The drag prop enables full 2D dragging. The user clicks and drags, and the element follows the cursor. When released, dragTransition defines the kinetic behavior: power (deceleration rate, 0–1) and restDelta (how close to zero before stopping).

Constrained Dragging

Constrain dragging to a specific area with dragConstraints. This can be a ref to a parent container or fixed pixel values:

import { motion } from 'framer-motion';
import React from 'react';

export function ConstrainedDrag() {
const containerRef = React.useRef(null);

return (
<div
ref={containerRef}
style={{
width: '300px',
height: '300px',
backgroundColor: '#f0f0f0',
borderRadius: '8px',
position: 'relative',
border: '2px solid #3498db'
}}
>
<motion.div
drag
dragConstraints={containerRef}
dragElastic={0.2}
style={{
width: '60px',
height: '60px',
backgroundColor: '#e74c3c',
borderRadius: '8px',
cursor: 'grab'
}}
/>
</div>
);
}

The red square can only be dragged within the blue container. If the user drags past the boundary, the element snaps back elastically (controlled by dragElastic).

You can also use fixed pixel constraints:

dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }}

This allows 100px movement left/right and 50px up/down from the starting position.

Kinetic Motion with Inertia

When you release a dragged element, Framer Motion applies inertia based on the velocity at release. The element coasts in the drag direction, decelerating smoothly:

import { motion } from 'framer-motion';
import React from 'react';

export function KineticSlider() {
const containerRef = React.useRef(null);

return (
<div
ref={containerRef}
style={{
width: '400px',
height: '100px',
backgroundColor: '#ecf0f1',
borderRadius: '8px',
overflow: 'hidden',
position: 'relative'
}}
>
<motion.div
drag="x"
dragConstraints={containerRef}
dragElastic={0.2}
dragTransition={{
power: 0.3, // Deceleration: higher = slower coast
restDelta: 0.001 // Stop when velocity < 0.001px/frame
}}
style={{
width: '800px',
height: '100px',
display: 'flex',
gap: '10px',
padding: '10px',
backgroundColor: '#3498db'
}}
>
{['Slide 1', 'Slide 2', 'Slide 3', 'Slide 4'].map((slide, i) => (
<div
key={i}
style={{
width: '150px',
height: '80px',
backgroundColor: '#fff',
borderRadius: '4px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}}
>
{slide}
</div>
))}
</motion.div>
</div>
);
}

Drag the slider left or right and release. It will coast in the direction of the swipe, decelerating smoothly until restDelta is reached. The power setting controls how quickly it decelerates (0 = instant stop, 1 = very slow coast).

Responding to Drag Events

Use onDragStart, onDrag, and onDragEnd callbacks to track drag state and respond to velocity:

import { motion } from 'framer-motion';
import React from 'react';

export function DragWithCallbacks() {
const [isDragging, setIsDragging] = React.useState(false);
const [velocity, setVelocity] = React.useState(0);

return (
<div>
<p>Dragging: {isDragging ? 'Yes' : 'No'}</p>
<p>Release velocity: {velocity.toFixed(2)} px/s</p>

<motion.div
drag
dragElastic={0.2}
dragTransition={{ power: 0.3, restDelta: 0.001 }}
onDragStart={() => setIsDragging(true)}
onDragEnd={(e, info) => {
setIsDragging(false);
setVelocity(Math.sqrt(info.velocity.x ** 2 + info.velocity.y ** 2));
}}
style={{
width: '100px',
height: '100px',
backgroundColor: isDragging ? '#e74c3c' : '#3498db',
borderRadius: '8px',
cursor: 'grab',
userSelect: 'none'
}}
>
Drag me
</motion.div>
</div>
);
}

The info object passed to onDragEnd contains velocity (x and y components in pixels per second). You can use this to decide whether to snap to a grid, trigger an action, or animate to a specific position.

Swipeable Cards Pattern

A common pattern is swiping cards left/right to dismiss or like them. Combine drag with onDragEnd to detect velocity and animate to an exit:

import { motion, AnimatePresence } from 'framer-motion';
import React from 'react';

export function SwipeCard() {
const [cards, setCards] = React.useState([
{ id: 1, text: 'Card 1' },
{ id: 2, text: 'Card 2' },
{ id: 3, text: 'Card 3' }
]);

const handleDragEnd = (e, info, id) => {
// If swiped more than 50px to the right or velocity > 500 px/s
if (
info.offset.x > 50 ||
(info.offset.x > 0 && info.velocity.x > 500)
) {
setCards((prev) => prev.filter((card) => card.id !== id));
}
};

return (
<AnimatePresence>
{cards.map((card) => (
<motion.div
key={card.id}
drag="x"
dragElastic={0.2}
onDragEnd={(e, info) => handleDragEnd(e, info, card.id)}
exit={{ opacity: 0, x: 500 }}
style={{
padding: '20px',
backgroundColor: '#fff',
borderRadius: '12px',
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
cursor: 'grab',
marginBottom: '15px',
touchAction: 'none'
}}
>
<h4>{card.text}</h4>
<p>Drag left to dismiss</p>
</motion.div>
))}
</AnimatePresence>
);
}

Drag a card to the right. Once the offset exceeds 50px or the velocity exceeds 500 px/s, the card is removed with an exit animation. This pattern is used in dating apps, card sorting UIs, and task management apps.

Drag with Snap Points

Snap a dragged element to predefined positions using dragConstraints combined with onDragEnd:

import { motion } from 'framer-motion';
import React from 'react';

export function SnapPoints() {
const [x, setX] = React.useState(0);
const snapPoints = [0, 150, 300];

return (
<div>
<p>Position: {x}px</p>

<motion.div
drag="x"
dragConstraints={{ left: 0, right: 300 }}
dragElastic={0.2}
onDragEnd={(e, info) => {
const closest = snapPoints.reduce((prev, curr) => {
return Math.abs(curr - info.offset.x) <
Math.abs(prev - info.offset.x)
? curr
: prev;
});
setX(closest);
}}
animate={{ x }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
style={{
width: '80px',
height: '80px',
backgroundColor: '#3498db',
borderRadius: '8px',
cursor: 'grab'
}}
/>

<div style={{ marginTop: '30px', display: 'flex', gap: '10px' }}>
{snapPoints.map((point) => (
<div
key={point}
style={{
position: 'absolute',
left: `${point}px`,
top: '280px',
width: '4px',
height: '20px',
backgroundColor: '#7f8c8d'
}}
/>
))}
</div>
</div>
);
}

Drag the box left or right. When released, it snaps to the nearest snap point. This pattern is used in slider controls, tab switchers, and progress indicators.

Key Takeaways

  • Enable dragging with drag="x", drag="y", or drag={true} for both axes.
  • Constrain dragging with dragConstraints to a parent ref or fixed pixel boundaries.
  • Kinetic motion applies inertia on release via dragTransition with power (deceleration) and restDelta (stopping threshold).
  • Use onDragEnd callback with info.velocity to detect swipes and trigger actions (dismiss, snap, navigate).
  • Combine drag with AnimatePresence for swipeable card patterns and dismissible UI elements.

Frequently Asked Questions

How do I know if a drag was a swipe vs a drag?

Check info.offset (distance) and info.velocity in onDragEnd. If the velocity is high (> 500 px/s) even with small offset, it's a swipe. If offset is large but velocity is low, it's a slow drag.

Can I drag and animate to a specific position after release?

Yes. In onDragEnd, calculate the target position and use animate with x and y to move there with a spring or tween transition.

Does dragging work on touch devices?

Yes. Framer Motion handles both mouse and touch events automatically. Set touchAction: 'none' in the style to prevent browser defaults (scrolling, zooming) interfering with drag.

How do I limit drag speed to prevent flinging too fast?

Use dragTransition.power to control deceleration. Lower power (0.1–0.2) causes faster stops; higher power (0.4–0.5) allows longer coasting. Adjust restDelta to control the stopping threshold.

Further Reading