Draggable
Draggable is a GSAP plugin that makes any element draggable, spinnable, or throwable with mouse and touch input. You hand it a target and a `type` (`x`, `y`, `x,y`, `rotation`, or `scroll`), and it handles the pointer math, bounds clamping, and momentum so you don't write a single mousemove listener.
Updated June 30, 2026
How Draggable works
You create a Draggable with Draggable.create(target, config). It returns an array of Draggable instances, one per matched element. The type decides what the drag actually moves: "x,y" translates the element, "rotation" spins it around its transform origin, "scroll" drags a scroll position.
bounds constrains the drag. Pass an element or selector to keep it inside that box, or an object like { minX: 0, maxX: 400 } for explicit limits. edgeResistance (0 to 1) controls how hard it pulls back when you drag past those bounds, so the element feels rubbery instead of hitting a wall.
Throw needs InertiaPlugin
inertia: true does nothing on its own. You have to register InertiaPlugin alongside Draggable, otherwise release just stops dead with no momentum. This is the single most common 'why won't it throw' bug.For snapping, pass snap. A liveSnap snaps continuously during the drag, while snap only resolves on release (and on the throw if inertia is on). You can give it an array of allowed values or a function that returns the nearest valid position.
Use Draggable for
- Drag-to-scrub sliders and carousels (drag a track left and right, throw it, let it glide to rest)
- Rotary controls (a
type: "rotation"knob or dial that snaps to notches) - Throwable canvases (an infinite grid or moodboard you fling around with momentum)
- Reorderable lists and cards where you need pointer position plus bounds in one call
- Any place you'd otherwise hand-write
pointerdown/pointermove/pointerupplus velocity tracking
Use something else when
- You only need swipe direction, not position -
Observernormalizes wheel, touch, and pointer intoonLeft/onRightcallbacks with less weight - You're moving items between drop zones with HTML semantics - native HTML drag-and-drop (or a list library) handles the data transfer Draggable doesn't
- The motion is purely scroll-position driven - that's
ScrollTrigger, not a drag interaction - You just want a value to coast to a stop from its current velocity -
InertiaPlugin.trackplus aninertia: "auto"tween does that without a draggable element
Used in these Annnimate components
Draggable is the input layer under every direct-manipulation component in the library. Three that lean on it:
- The Circular Slider uses
type: "rotation"with snap so the handle spins around the dial and settles on the nearest step - The Infinite Draggable Grid uses
type: "x,y"withinertia: trueso you can fling the whole grid and watch it glide to a stop - The Infinite Parallax Slider drives its track from drag velocity, with the throw momentum feeding the parallax offset between layers
Free since GSAP 3.13
See it running in production
Common questions
- Why isn't my Draggable throwing after I release it?
- You almost certainly forgot InertiaPlugin.
inertia: trueonly works whenInertiaPluginis registered alongside Draggable:gsap.registerPlugin(Draggable, InertiaPlugin). Without it the element stops the instant you let go, with no momentum. If it's registered and still not throwing, check that the element actually moved fast enough at release, since a slow drag produces almost no throw distance. - How do I make a Draggable snap to specific positions?
- Use the
snapconfig. Pass an array of allowed values, or a function that receives the current value and returns the nearest valid one.snapresolves on release (and at the end of the throw if inertia is on). If you want it to lock to positions continuously while dragging, useliveSnapinstead. - Draggable vs native HTML drag-and-drop, which should I use?
- Different jobs. Native HTML drag-and-drop is built for moving data between drop targets (files, list items, anything with a dataTransfer payload), and it gives you the OS drag affordances. Draggable is for free-form direct manipulation: sliders, dials, throwable surfaces, anything where you care about smooth pixel-level position, bounds, and momentum. If you're flinging a grid around, that's Draggable. If you're dropping a row into a column, that's native (or a list library).
- Can I use Draggable inside React or Next.js?
- Yes. Create it inside
useGSAPso it's set up after the DOM exists and torn down on unmount. Register the plugins client-side (wrapgsap.registerPlugin(Draggable, InertiaPlugin)in atypeof window !== 'undefined'guard or call it in the effect). Kill the instance in cleanup:Draggable.createreturns an array, so hold the reference and callinstance.kill()when the component unmounts, otherwise the listeners leak across navigations. - How do I keep a Draggable element inside its container?
- Set
boundsto the container element or selector and Draggable clamps the drag to that box. For a softer edge, addedgeResistancebetween 0 and 1 so the element resists going past the bounds instead of stopping hard. You can also pass explicit limits as an object, likebounds: { minX: 0, maxX: 500, minY: 0, maxY: 0 }, when there's no wrapping element to measure against.
