Lightweight JavaScript Draggable Element Library


Make panels, cards, chips, and layout blocks draggable in plain HTML and JavaScript. hcg-draggable is a zero-dependency library (~10 KB minified) built on the Pointer Events API, so mouse, touch, and pen input share one code path. Movement uses CSS transform: translate() for smooth dragging without layout reflow.

On this page you will find live demos, installation (direct download, npm, CDN), a full options reference, data-attribute setup, React and TypeScript notes, and copy-paste examples for handles, containment, grid snap, and revert. Open the full interactive demo page for all ten try-it-yourself examples.

What is hcg-draggable?

hcg-draggable is a lightweight vanilla JavaScript library for making any element draggable. Call hcgDraggable() on a selector or use a data-hcg-draggable attribute, constrain movement with axis lock and containment, or revert back on release - without React, jQuery, or any other dependency.

Why use hcg-draggable?

Most drag solutions are either tied to a framework or ship a full sortable / drag-and-drop system when you only need to move panels around. jQuery UI Draggable works, but it depends on jQuery and positions with left/top instead of transform, which can trigger layout reflow on every move.

hcg-draggable fills that gap: a small, dependency-free library built on Pointer Events for mouse, touch, and pen. It suits vanilla pages, React or Vue dashboards, and floating panels without pulling in a heavy toolkit. See the comparison table for how it differs from jQuery UI and hand-rolled scripts, and Features for the full capability list.

Live Demo

Drag the boxes below. Each one shows a different option. For every demo in one place, see the full interactive demo page.

Free drag

Drag the box anywhere on the page.

drag me
Axis lock (horizontal only)

This box can only move left and right.

x only
Snap to a 40px grid

Movement snaps to 40 pixel steps.

grid 40
Containment (stay inside the dashed area)
contained
Handle only

Only the title bar starts a drag. The body stays interactive.

:: title bar (drag here)
The body is not draggable.
Drag threshold, cancel and click suppression

This whole box is draggable, but a drag only begins after the pointer moves 5 pixels, so the button still clicks normally. The button is also listed in cancel, so pressing it never starts a drag, and the click that follows a real drag is swallowed automatically.

Drag me, or click the button.

Revert on release

With revert: true, the element animates back to its start position when released.

revert me

Comparison Table

How hcg-draggable compares with common alternatives.

Feature hcg-draggable jQuery UI draggable Hand-coded drag
File size~3 KB min + gzip (one JS file)~90 KB+ (jQuery + jQuery UI)Varies
DependenciesNonejQuery + jQuery UINone
Touch and pen supportYes (Pointer Events)Needs extra pluginManual
Moves with transformYesNo (left/top)Manual
Drag handleYesYesManual
Axis lockYesYesManual
Grid snappingYesYesManual
ContainmentYesYesManual
Bring to front on grabYesNoManual
Revert on releaseYesYesManual

Installation

Direct download

Source code and package:

Or download hcg-draggable.js and hcg-draggable.css and include them directly:

HTML
<link rel="stylesheet" href="hcg-draggable.css">
<script src="hcg-draggable.js"></script>
NPM

Install from npm:

Command Line
npm install hcg-draggable

CommonJS (require):

JavaScript
require("hcg-draggable/hcg-draggable.css");
const hcgDraggable = require("hcg-draggable");

ESM (import):

JavaScript
import hcgDraggable from "hcg-draggable";
import "hcg-draggable/hcg-draggable.css";
CDN usage

Load from a CDN with two tags no npm or bundler required. The global hcgDraggable function is available as soon as the script finishes loading.

jsDelivr:

HTML
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hcg-draggable@1/hcg-draggable.css">
<script src="https://cdn.jsdelivr.net/npm/hcg-draggable@1/hcg-draggable.js"></script>

unpkg:

HTML
<link rel="stylesheet" href="https://unpkg.com/hcg-draggable@1/hcg-draggable.css">
<script src="https://unpkg.com/hcg-draggable@1/hcg-draggable.js"></script>

Replace @1 or @1.0.0 with the release you want. Load CSS in <head> before the script. You can also self-host by uploading hcg-draggable.js and hcg-draggable.css to your own server.

Basic Usage

The quickest way is to add the data-hcg-draggable attribute. Elements with this attribute are made draggable automatically when the page loads.

HTML
<div class="drag-me" data-hcg-draggable>drag me</div>
Try it live: free drag

Or call hcgDraggable() with a target and options. The target can be a CSS selector, a DOM element, or a NodeList. When the selector matches several elements, an array of instances is returned; a single match returns one instance. Calling the factory again on the same element returns the existing instance instead of creating a duplicate.

JavaScript
hcgDraggable("#box", {
  axis: null,
  handle: ".title-bar",
  containment: "parent",
  grid: [20, 20],
  revert: false,
  onEnd: function (e) { console.log(e.x, e.y, e.moved); }
});
Drag by a handle

Pass a handle selector so only that child element starts a drag. The rest of the element stays interactive.

HTML
<div class="drag-me" id="panel">
  <div class="bar">Title bar</div>
  <div class="body">Content here</div>
</div>
JavaScript
hcgDraggable("#panel", {
    handle: ".bar"
});

Make clicks still work (threshold and cancel)

When a draggable element also needs to be clicked, set a small dragThreshold so a drag only begins after the pointer moves that many pixels. The click after a real drag is swallowed automatically, so a draggable button does not also fire its click handler. Use cancel to keep specific children, such as buttons or inputs, from starting a drag at all.

JavaScript
hcgDraggable("#card", {
  dragThreshold: 5,        // start dragging only after a 5px move
  cancel: "button, input"  // these children never start a drag
});

Try it live: drag threshold & cancel


Lock to one axis

Set axis to "x" for horizontal only or "y" for vertical only. Leave it null to move freely.

JavaScript
hcgDraggable("#slider", { axis: "x" });

Try it live: axis lock


Keep inside a container (containment)

Set containment to constrain dragging. Use "parent", "viewport", a selector or element, or explicit pixel offsets.

JavaScript
hcgDraggable("#box", { containment: "parent" });
hcgDraggable("#box", { containment: "#container" });
hcgDraggable("#box", { containment: { top: 0, left: 0, right: 300, bottom: 200 } });

Try it live: containment


Snap to a grid

Pass a grid step as [x, y] to align movement.

JavaScript
hcgDraggable("#box", { grid: [25, 25] });

Try it live: grid snap


Change options at runtime

Call setOption(name, value) on an instance to change any option after creation. It re-applies the needed side effects, such as re-clamping when containment changes or enabling/disabling.

JavaScript
const d = hcgDraggable("#box");
d.setOption("axis", "x");      // lock to horizontal
d.setOption("containment", "parent");
d.setOption("disabled", true); // turn dragging off

Stay inside containment after a resize

Contained elements are clamped on creation and again whenever the window resizes, so they never end up stranded outside their container. You can also re-clamp manually after changing the layout.

JavaScript
var d = hcgDraggable("#box", { containment: "parent" });
d.contain(); // pull back inside the container now

Try it live: containment on resize


Revert to start position

Set revert: true to animate the element back when released.

JavaScript
hcgDraggable("#box", { revert: true });

Try it live: revert on release

Features

Everything included in the library at a glance:

  • Zero dependencies - one JS file and one CSS file.
  • Pointer Events - mouse, touch, and pen through one code path.
  • Transform based - smooth dragging with no layout reflow.
  • Drag handles - restrict the grab area with a selector.
  • Cancel regions - exclude buttons, links, or inputs from starting a drag.
  • Drag threshold - require a small move before dragging so clicks still work.
  • Click suppression - the click after a real drag is swallowed automatically.
  • Axis locking - limit movement to x or y.
  • Grid snapping - align movement to a step.
  • Containment - parent, viewport, an element, or pixel offsets; re-clamped on window resize.
  • Bring to front - the grabbed element rises above other draggables.
  • Revert - animate back on release when revert: true.
  • Runtime options - change any option later with setOption().
  • Data attributes (no per-element JS) - data-hcg-draggable plus option attributes; auto-init on page load.
  • Callbacks - onStart, onMove, and onEnd with position data.
  • Instance API - enable, disable, setOption, setPosition, contain, reset, and destroy.
  • Tiny footprint - ~3 KB minified; works in every modern browser.

Using with React

Create the draggable inside a useEffect hook and clean it up with destroy when the component unmounts.

JavaScript
import { useEffect, useRef } from "react";
import hcgDraggable from "hcg-draggable";
import "hcg-draggable/hcg-draggable.css";

function Panel() {
  const boxRef = useRef(null);
  const dragRef = useRef(null);

  useEffect(() => {
    dragRef.current = hcgDraggable(boxRef.current, {
      handle: ".bar",
      containment: "parent",
      onEnd: (e) => console.log("end", e.x, e.y, e.moved)
    });
    return () => dragRef.current.destroy();
  }, []);

  return (
    <div ref={boxRef}>
      <div className="bar">Drag here</div>
      <div>Panel content</div>
    </div>
  );
}

Options Reference

OptionTypeDefaultDescription
handleStringnullCSS selector for a child element that starts the drag. When set, only that element is grabbable.
cancelStringnullCSS selector inside the element that must not start a drag, such as buttons, links or inputs. The opposite of handle.
dragThresholdNumber0Pixels the pointer must move before a drag actually starts. A small value (for example 5) lets normal clicks on the element still work.
axisStringnull"x" locks to horizontal, "y" locks to vertical, null allows both directions.
containmentString, Element or ObjectnullArea to keep the element inside. Accepts "parent", "viewport", a selector, an element, or an object with top, left, right and bottom pixel offsets.
gridArraynullSnap step as [stepX, stepY] in pixels.
disabledBooleanfalseCreate the instance in a disabled state. Call enable() to turn it on.
bringToFrontBooleantrueRaise the element's z-index above other draggables when a drag starts.
revertBooleanfalseAnimate back to the start position on release.
onStartFunctionnullCalled when a drag begins. Receives { x, y, event, target }.
onMoveFunctionnullCalled on every move. Receives { x, y, event, target }.
onEndFunctionnullCalled when the drag ends. Receives { x, y, event, target, moved }.

Instance Methods

The factory returns a draggable instance with the following methods. When the target matches several elements, an array of instances is returned instead.

MethodDescription
enable()Turns dragging on. Returns the instance for chaining.
disable()Turns dragging off without removing listeners. Returns the instance.
setOption(name, value)Changes an option after creation and re-applies side effects (handle, containment, disabled). Returns the instance.
getPosition()Returns the current position as { x, y }.
setPosition(x, y)Moves the element to the given translate position.
contain()Re-clamps the current position inside the containment area. Runs automatically on init and window resize.
reset()Moves the element back to its starting position { x: 0, y: 0 }.
destroy()Removes all listeners and classes. Use this for SPA or framework cleanup.

Read or set the translate position programmatically:

JavaScript
var d = hcgDraggable("#box");
var pos = d.getPosition();   // { x: 0, y: 0 }
d.setPosition(120, 40);      // move instantly
d.reset();                   // back to { x: 0, y: 0 }

Static methods are available on the factory.

JavaScript
hcgDraggable.init();        // make every [data-hcg-draggable] element draggable
hcgDraggable.destroyAll();  // destroy every active instance

Data Attributes

Every option that takes a simple value can be set straight in the HTML, so you can use the library without writing any JavaScript. Elements carrying data-hcg-draggable are initialized automatically on page load.

AttributeMaps to optionExample
data-hcg-draggableenables auto-initdata-hcg-draggable
data-hcg-handlehandledata-hcg-handle=".bar"
data-hcg-cancelcanceldata-hcg-cancel="button, input"
data-hcg-thresholddragThresholddata-hcg-threshold="5"
data-hcg-axisaxisdata-hcg-axis="x"
data-hcg-containmentcontainmentdata-hcg-containment="parent"
data-hcg-gridgriddata-hcg-grid="20" or "20,40"
data-hcg-disableddisableddata-hcg-disabled
data-hcg-bring-to-frontbringToFrontdata-hcg-bring-to-front="false"
data-hcg-revertrevertdata-hcg-revert

Full markup example using several attributes together:

HTML
<div
  data-hcg-draggable=""
  data-hcg-handle=".bar"
  data-hcg-containment="parent"
  data-hcg-grid="20">
  <div class="bar">Title bar</div>
  <div>Content</div>
</div>

To initialize elements added to the page later, call hcgDraggable.init() again, or call the factory directly on the new element.

Events

Callbacks are exposed as options. Each one receives a single object with the current position, the original pointer event, and the dragged element.

EventFired whenArgument
onStartA drag begins (pointer down on the element or handle).{ x, y, event, target }
onMoveThe pointer moves during a drag.{ x, y, event, target }
onEndThe drag ends (pointer up or cancel).{ x, y, event, target, moved }

The x and y values are the current translate offset in pixels. The moved flag is true when the pointer actually moved during the drag.

JavaScript
hcgDraggable("#box", {
  onStart: function (e) { console.log("start", e.x, e.y); },
  onMove:  function (e) { console.log("move",  e.x, e.y); },
  onEnd:   function (e) { console.log("end", e.moved); }
});

Browser Support

hcg-draggable works in all modern browsers that support the Pointer Events API.

BrowserSupported
Google ChromeYes
Mozilla FirefoxYes
Microsoft EdgeYes
SafariYes
OperaYes
Mobile browsersYes (touch dragging supported)

License

Released under the MIT License. Free for personal and commercial use. Copyright HTML Code Generator.

Frequently Asked Questions (FAQ)

Does hcg-draggable have any dependencies?

No. hcg-draggable is plain vanilla JavaScript with zero dependencies. Include hcg-draggable.js and hcg-draggable.css and you are ready to go.

Does hcg-draggable work on touch devices?

Yes. It is built on the Pointer Events API, so mouse, touch, and pen input all work through one code path. Draggable elements set touch-action: none so dragging does not scroll the page accidentally.

Can I limit dragging to one axis or keep an element inside a container?

Yes. Set axis to "x" or "y" to lock movement to one direction. Set containment to "parent", "viewport", a selector, an element, or pixel offsets to keep the element inside an area.

Can I use hcg-draggable with React?

Yes. Create the draggable inside a useEffect hook, keep the returned instance in a ref, and call destroy() in the cleanup function when the component unmounts. See Using with React on this page.

Can I load hcg-draggable from a CDN?

Yes. Add <link> and <script> tags for hcg-draggable.css and hcg-draggable.js from jsDelivr, unpkg, or your own hosted copy, then call hcgDraggable('.selector'). See Installation and CDN usage on this page.