Drag
Any motion component can be made draggable with the drag prop. The component gets full physics-based momentum, elastic constraints, and gesture callbacks.
<motion.div drag /><motion.div drag />Drag the box around
Usage
Set drag to true to enable dragging on both axes:
<motion.div drag /><motion.div drag />To restrict dragging to a single axis, pass "x" or "y":
<motion.div drag="x" /><motion.div drag="x" />Constraints
Drag movement can be constrained with the dragConstraints prop. Constraints can be defined as pixel offsets from the element’s origin, or as a reference to another element.
Pixel constraints
Pass an object with top, left, right, and bottom values (in pixels):
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }}
/><motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }}
/>Element constraints
Pass an element reference to constrain dragging within that element’s bounding box:
<script lang="ts">
import { motion } from '@humanspeak/svelte-motion'
let bounds: HTMLElement | null = null
</script>
<div bind:this={bounds}>
<motion.div drag dragConstraints={bounds} />
</div><script lang="ts">
import { motion } from '@humanspeak/svelte-motion'
let bounds: HTMLElement | null = null
</script>
<div bind:this={bounds}>
<motion.div drag dragConstraints={bounds} />
</div>Constrained to the dashed area
Elastic
By default, dragging beyond constraints has an elastic feel. Control this with dragElastic:
<!-- No elasticity — hard stop at constraints -->
<motion.div drag dragConstraints={{ left: -100, right: 100 }} dragElastic={0} />
<!-- Full elasticity (default ~0.35) -->
<motion.div drag dragConstraints={{ left: -100, right: 100 }} dragElastic={1} /><!-- No elasticity — hard stop at constraints -->
<motion.div drag dragConstraints={{ left: -100, right: 100 }} dragElastic={0} />
<!-- Full elasticity (default ~0.35) -->
<motion.div drag dragConstraints={{ left: -100, right: 100 }} dragElastic={1} />Set dragElastic to 0 for a hard boundary, or 1 for maximum stretch. Values between 0 and 1 control the amount of elastic overdrag.
Momentum
After releasing a drag, the element continues with momentum by default. Disable this with dragMomentum:
<motion.div drag dragMomentum={false} /><motion.div drag dragMomentum={false} />Fine-tune the physics with dragTransition:
<motion.div
drag
dragTransition={{
bounceStiffness: 300,
bounceDamping: 20,
power: 0.8,
timeConstant: 200
}}
/><motion.div
drag
dragTransition={{
bounceStiffness: 300,
bounceDamping: 20,
power: 0.8,
timeConstant: 200
}}
/>Snap to origin
Set dragSnapToOrigin to spring the element back to its starting position on release:
<motion.div
drag
dragSnapToOrigin
whileDrag={{ scale: 1.05, cursor: 'grabbing' }}
/><motion.div
drag
dragSnapToOrigin
whileDrag={{ scale: 1.05, cursor: 'grabbing' }}
/>Direction lock
When dragDirectionLock is enabled and both axes are allowed, the drag locks to whichever axis the user moves first (threshold: 4px):
<script lang="ts">
import { motion } from '@humanspeak/svelte-motion'
let lockedAxis = $state<'x' | 'y' | null>(null)
</script>
<motion.div
drag
dragDirectionLock
onDirectionLock={(axis) => (lockedAxis = axis)}
/><script lang="ts">
import { motion } from '@humanspeak/svelte-motion'
let lockedAxis = $state<'x' | 'y' | null>(null)
</script>
<motion.div
drag
dragDirectionLock
onDirectionLock={(axis) => (lockedAxis = axis)}
/>Drag controls
Use createDragControls to start a drag from an external element, like a drag handle:
<script lang="ts">
import { motion, createDragControls } from '@humanspeak/svelte-motion'
const controls = createDragControls()
</script>
<button onpointerdown={(e) => controls.start(e)}>Drag handle</button>
<motion.div drag dragControls={controls} dragListener={false} /><script lang="ts">
import { motion, createDragControls } from '@humanspeak/svelte-motion'
const controls = createDragControls()
</script>
<button onpointerdown={(e) => controls.start(e)}>Drag handle</button>
<motion.div drag dragControls={controls} dragListener={false} />Setting dragListener={false} prevents the element itself from initiating the drag — only the external handle will work.
The start method accepts an optional options object:
controls.start(event, { snapToCursor: true })controls.start(event, { snapToCursor: true })While dragging
Use whileDrag to animate the element while a drag gesture is active:
<motion.div
drag
whileDrag={{ scale: 1.1, boxShadow: '0 8px 24px rgba(0,0,0,0.2)' }}
/><motion.div
drag
whileDrag={{ scale: 1.1, boxShadow: '0 8px 24px rgba(0,0,0,0.2)' }}
/>Propagation
By default, drag events propagate to parent drag components. Set dragPropagation={false} to prevent this:
<motion.div drag>
<motion.div drag dragPropagation={false} />
</motion.div><motion.div drag>
<motion.div drag dragPropagation={false} />
</motion.div>Callbacks
Drag provides several lifecycle callbacks. Each receives the native PointerEvent and a DragInfo object:
<motion.div
drag
onDragStart={(event, info) => console.log('Start', info.point)}
onDrag={(event, info) => console.log('Move', info.delta)}
onDragEnd={(event, info) => console.log('End', info.velocity)}
onDragTransitionEnd={() => console.log('Momentum settled')}
/><motion.div
drag
onDragStart={(event, info) => console.log('Start', info.point)}
onDrag={(event, info) => console.log('Move', info.delta)}
onDragEnd={(event, info) => console.log('End', info.velocity)}
onDragTransitionEnd={() => console.log('Momentum settled')}
/>DragInfo
The info object passed to drag callbacks contains:
| Property | Type | Description |
|---|---|---|
point | { x, y } | Current pointer position |
delta | { x, y } | Change since the last drag event (per-frame delta) |
offset | { x, y } | Cumulative transform offset since drag start |
velocity | { x, y } | Current velocity in px/s |
Props
Drag
| Prop | Type | Default | Description |
|---|---|---|---|
drag | boolean \| 'x' \| 'y' | false | Enable dragging. true for both axes, or restrict to one axis |
dragConstraints | { top?, left?, right?, bottom? } \| HTMLElement | — | Pixel bounds or element reference to constrain drag |
dragElastic | number | 0.35 | Elasticity when overdragging. 0 = hard stop, 1 = full stretch |
dragMomentum | boolean | true | Apply momentum after release |
dragTransition | DragTransition | — | Physics configuration for momentum and boundaries |
dragSnapToOrigin | boolean | false | Spring back to starting position on release |
dragDirectionLock | boolean | false | Lock to first detected axis of movement |
dragPropagation | boolean | true | Allow drag events to bubble to parent drag elements |
dragListener | boolean | true | Enable the built-in drag listener on the element |
dragControls | DragControls | — | External controls for programmatic drag initiation |
whileDrag | MotionWhileDrag | — | Animation target while drag is active |
Callbacks
| Prop | Type | Description |
|---|---|---|
onDragStart | (event, info) => void | Fires when drag begins |
onDrag | (event, info) => void | Fires on every drag movement |
onDragEnd | (event, info) => void | Fires when drag ends |
onDirectionLock | (axis: 'x' \| 'y') => void | Fires when direction is locked |
onDragTransitionEnd | () => void | Fires when post-drag momentum settles |
DragTransition
| Property | Type | Default | Description |
|---|---|---|---|
bounceStiffness | number | 200 | Spring stiffness at constraint boundaries |
bounceDamping | number | 40 | Damping at constraint boundaries |
power | number | 0.8 | Momentum power factor |
timeConstant | number | 700 | Exponential decay time constant (ms) |
restDelta | number | 0.5 | Distance threshold for “at rest” |
restSpeed | number | 10 | Velocity threshold for “at rest” |
min | number | — | Minimum value for axis |
max | number | — | Maximum value for axis |
Related
- Motion Component — Full API reference
- Variants — Define reusable animation states
- AnimatePresence — Exit animations on unmount
Based on Motion’s drag API.