Plugins

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

Mechanics

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.

script.js

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.

script.js
When

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 / pointerup plus velocity tracking
Alternatives

Use something else when

  • You only need swipe direction, not position - Observer normalizes wheel, touch, and pointer into onLeft / onRight callbacks 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.track plus an inertia: "auto" tween does that without a draggable element
In production

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" with inertia: true so 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

Draggable and InertiaPlugin used to sit behind the paid Club GSAP membership. Webflow made GSAP 100% free, core plus every former Club plugin, with commercial use, fully live April 30 2025 (Webflow blog, 2025). So you can ship throw-with-momentum without a license check, which is a big part of why drag interactions show up on so many recent brand sites.
Used in components

See it running in production

FAQ

Common questions

Why isn't my Draggable throwing after I release it?
You almost certainly forgot InertiaPlugin. inertia: true only works when InertiaPlugin is 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 snap config. Pass an array of allowed values, or a function that receives the current value and returns the nearest valid one. snap resolves on release (and at the end of the throw if inertia is on). If you want it to lock to positions continuously while dragging, use liveSnap instead.
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 useGSAP so it's set up after the DOM exists and torn down on unmount. Register the plugins client-side (wrap gsap.registerPlugin(Draggable, InertiaPlugin) in a typeof window !== 'undefined' guard or call it in the effect). Kill the instance in cleanup: Draggable.create returns an array, so hold the reference and call instance.kill() when the component unmounts, otherwise the listeners leak across navigations.
How do I keep a Draggable element inside its container?
Set bounds to the container element or selector and Draggable clamps the drag to that box. For a softer edge, add edgeResistance between 0 and 1 so the element resists going past the bounds instead of stopping hard. You can also pass explicit limits as an object, like bounds: { minX: 0, maxX: 500, minY: 0, maxY: 0 }, when there's no wrapping element to measure against.