Touch-Friendly Drag and Drop Library for Vanilla JS – Dragster.js

Category: Drag & Drop , Javascript | May 26, 2026
Authorsunpietro
Last UpdateMay 26, 2026
LicenseMIT
Tags
Views0 views
Touch-Friendly Drag and Drop Library for Vanilla JS – Dragster.js

Dragster.js is a tiny vanilla JavaScript drag and drop library that enables your users to move, replace, and clone DOM elements between drop regions you specify.

It handles mouse and touch input, drop placeholders, draggable wrappers, optional cloning, and callback-based control during the drag lifecycle.

You can use this library to create drag and drop UI such as kanbans boards, sortable content groups, product pickers, block-based editors, and simple admin layout tools.

Features

  • Move elements between custom drop regions.
  • Replace dropped elements with target elements.
  • Clone items from drag-only source regions.
  • Restrict dragging to handle elements.
  • Support mouse input on desktop screens.
  • Support touch input on mobile devices.
  • Refresh draggable items after DOM changes.
  • Refresh drop regions after dynamic layout changes.
  • Clean up event listeners during teardown.
  • Control drag behavior through callbacks.

How to use it:

1. Install Dragster with a package manager and import it into your JS:

# Yarn
$ yarn add dragsterjs
# NPM
$ npm install dragsterjs
import Dragster from 'dragsterjs';

2. You can also directly import the browser module when you host the module file yourself.

<script type="module">
import Dragster from './vendor/dragster.js';
</script>

3. Or load the minified script directly from unpkg and use the global Dragster:

<script src=”https://unpkg.com/dragsterjs/dragster.min.js”></script>

4. Create one or more drop regions and place draggable elements inside them.

<div class="task-lane" aria-label="Backlog tasks">
  <article class="task-card">Write docs</article>
  <article class="task-card">Review UI copy</article>
</div>
<div class="task-lane" aria-label="Done tasks">
  <article class="task-card">Ship homepage</article>
</div>

5. Add the required CSS rules. The first rule prevents accidental text selection during dragging. The second rule lets the drop zone under the shadow element receive pointer hover behavior.

[draggable] {
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
}
.dragster-temp {
  pointer-events: none;
}

6. Initialize Dragster.js with the selectors used in your markup.

const taskDrag = Dragster({
  // Select every item that users can drag.
  elementSelector: '.task-card',
  // Select every region that can receive items.
  regionSelector: '.task-lane'
});

7. A dashboard layout often needs item swapping instead of normal movement. The option below swaps the dragged block with the target block inside the selected region set.

const widgetDrag = Dragster({
  elementSelector: '.dashboard-widget',
  regionSelector: '.dashboard-column',
  replaceElements: true
});

8. A fixed-height board can break when the script recalculates lane height. This configuration keeps region height under your own CSS control.

const fixedBoardDrag = Dragster({
  elementSelector: '.kanban-ticket',
  regionSelector: '.kanban-lane',
  updateRegionsHeight: false
});

9. Dynamic interfaces often add cards after an API response or form submit. Call update() after you insert new draggable elements into an existing region.

const issueDrag = Dragster({
  elementSelector: '.issue-card',
  regionSelector: '.issue-column'
});
document.querySelector('#add-issue').addEventListener('click', function () {
  const issue = document.createElement('article');
  issue.className = 'issue-card';
  issue.textContent = 'New support ticket';
  document.querySelector('.issue-column').appendChild(issue);
  // Re-scan the DOM for new draggable elements.
  issueDrag.update();
});

10. Large cards often need a smaller drag handle to prevent accidental movement. The handle class must exist on the element that receives the initial mouse or touch press.

const handleDrag = Dragster({
  elementSelector: '.profile-card',
  regionSelector: '.profile-row',
  dragHandleCssClass: 'profile-card__grip'
});

11. A product shelf can let users add the same item to a cart multiple times. Mark the source region as drag-only and enable cloning.

<div class="product-shelf dragster-region--drag-only">
  <article class="shop-item" data-sku="keyboard-pro">Keyboard Pro</article>
  <article class="shop-item" data-sku="mouse-lite">Mouse Lite</article>
</div>
<div class="cart-zone" aria-label="Shopping cart"></div>
const cartDrag = Dragster({
  elementSelector: '.shop-item',
  regionSelector: '.product-shelf, .cart-zone',
  dragOnlyRegionCssClass: 'dragster-region--drag-only',
  cloneElements: true
});

Real-world Example:

A two-column task board needs markup, CSS, and JavaScript together. Keep each card inside a region before initialization, and load the module after the DOM exists.

<section class="work-board">
  <div class="work-lane" aria-label="Open work">
    <article class="work-card">Create pricing mockup</article>
    <article class="work-card">Fix mobile menu</article>
  </div>
  <div class="work-lane" aria-label="Completed work">
    <article class="work-card">Update footer links</article>
  </div>
</section>
<script type="module">
  import Dragster from './vendor/dragster.js';
  Dragster({
    elementSelector: '.work-card',
    regionSelector: '.work-lane',
    minimumRegionHeight: 120
  });
</script>
.work-board {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}
.work-lane {
  min-height: 120px;
  padding: 12px;
  border: 1px solid #d7dce2;
  border-radius: 8px;
}
.work-card {
  margin-bottom: 10px;
  padding: 12px;
  border: 1px solid #c7ccd4;
  border-radius: 6px;
  background: #fff;
  cursor: grab;
}
[draggable] {
  user-select: none;
}
.dragster-temp {
  pointer-events: none;
}

A block palette needs cloning because source blocks should stay available after each drop. The source region needs the drag-only class that matches your Dragster.js option.

<div class="builder-layout">
  <aside class="block-palette dragster-region--drag-only">
    <div class="builder-block">Hero Section</div>
    <div class="builder-block">Feature Grid</div>
    <div class="builder-block">Newsletter Form</div>
  </aside>
  <main class="page-canvas" aria-label="Page canvas"></main>
</div>
<script type="module">
  import Dragster from './vendor/dragster.js';
  const builderDrag = Dragster({
    elementSelector: '.builder-block',
    regionSelector: '.block-palette, .page-canvas',
    dragOnlyRegionCssClass: 'dragster-region--drag-only',
    cloneElements: true,
    onAfterDragDrop: function (event) {
      // clonedTo references the new block placed in the target region.
      if (event.dragster.clonedTo) {
        event.dragster.clonedTo.dataset.blockId = crypto.randomUUID();
      }
    }
  });
</script>

Configuration Options:

  • elementSelector (String): Selects the elements that Dragster.js should make draggable. The default value is '.dragster-block'.
  • regionSelector (String): Selects the drop regions where drag and drop behavior should run. The default value is '.dragster-region'.
  • dragHandleCssClass (String|Boolean): Restricts drag start to a specific CSS class when set to a class name. The default value is false, so the full element starts the drag.
  • replaceElements (Boolean): Switches the dragged element with the drop target instead of moving the dragged element into a placeholder. The default value is false.
  • cloneElements (Boolean): Clones the dragged element into the target region and keeps the original in place. This option requires a drag-only source region. The default value is false.
  • updateRegionsHeight (Boolean): Updates region height based on visible elements in each region. The default value is true.
  • minimumRegionHeight (Number): Sets the smallest height that Dragster.js should apply to a region during height updates. The default value is 60.
  • scrollWindowOnDrag (Boolean): Scrolls the window when the dragged item approaches the top or bottom of the viewport. The default value is false.
  • dragOnlyRegionCssClass (String): Marks source regions used for clone-only workflows. The default value is 'dragster-region--drag-only'.
  • wrapDraggableElements (Boolean): Wraps draggable elements in Dragster.js wrapper elements. The default value is true.
  • shadowElementUnderMouse (Boolean): Places the shadow element under the original pointer position instead of centering it on half of its width. The default value is false.
  • onBeforeDragStart (Function): Runs before a drag starts. Return false to cancel the drag.
  • onAfterDragStart (Function): Runs after a drag starts.
  • onBeforeDragMove (Function): Runs before each drag move. Return false to cancel the current movement.
  • onAfterDragMove (Function): Runs after each drag move.
  • onBeforeDragEnd (Function): Runs before a drag ends. Return false to cancel the drop.
  • onAfterDragEnd (Function): Runs after a drag ends.
  • onAfterDragDrop (Function): Runs after an element has been dropped, replaced, or cloned.

Callbacks receive one Dragster event object through event.dragster.

const auditDrag = Dragster({
  elementSelector: '.audit-card',
  regionSelector: '.audit-lane',
  onBeforeDragStart: function (event) {
    const draggedNode = event.dragster.drag.node;
    // Return false when your app needs to block this drag.
    return !draggedNode?.classList.contains('is-locked');
  },
  onAfterDragDrop: function (event) {
    console.log('Dragged node:', event.dragster.drag.node);
    console.log('Drop target:', event.dragster.drop.node);
    console.log('Dropped node:', event.dragster.dropped);
    console.log('Clone source:', event.dragster.clonedFrom);
    console.log('Clone target:', event.dragster.clonedTo);
  }
});

The callback object can include these fields.

{
  drag: { node },
  drop: { node },
  shadow: { node, top, left },
  placeholder: { node, position },
  dropped: node,
  clonedFrom: node,
  clonedTo: node
}

API Methods:

// Re-scan the DOM for draggable elements that match elementSelector.
taskDrag.update();
// Re-scan the DOM for drop regions that match regionSelector.
taskDrag.updateRegions();
// Remove Dragster.js event listeners before a component or page view is removed.
taskDrag.destroy();

Alternatives:

FAQs:

Q: Why does dropping feel wrong or fail over a region?
A: Add the required .dragster-temp { pointer-events: none; } rule. The shadow element can block hover detection when this CSS rule is missing.

Q: Can Dragster.js handle dynamically added cards?
A: Yes. Add the new DOM element, then call instance.update() so the library can re-scan draggable elements.

Q: Can Dragster.js work inside React, Vue, or Angular projects?
A: Yes, but you need to initialize it after the DOM nodes exist. Call destroy() when the component unmounts or the view changes.

Q: Can elements be dragged between two separate Dragster instances?
A: No. Each Dragster instance operates on its own scoped set of regions and elements. Place all participating regions under a single Dragster instance to allow movement between them.

You Might Be Interested In:


Leave a Reply