Touch-Friendly Drag and Drop Library for Vanilla JS – Dragster.js
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.
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
});
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>
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
} // 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();
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.
The post Touch-Friendly Drag and Drop Library for Vanilla JS – Dragster.js appeared first on CSS Script.
A critical heap buffer overflow vulnerability (CVE-2026-48095 / GHSL-2026-140) has been disclosed in 7-Zip version…
A critical heap buffer overflow vulnerability (CVE-2026-48095 / GHSL-2026-140) has been disclosed in 7-Zip version…
LANSING, MICH. (WOWO) — State lawmakers have approved legislation that would give Mackinac Island expanded…
CLEVELAND, OHIO (WOWO) — A new report from the Lake Carriers’ Association says U.S.-flagged shipping…
A critical heap buffer overflow vulnerability has been disclosed in 7-Zip version 26.00, enabling attackers…
Anthropic appears to be loosening its grip on Claude Mythos, the company’s most powerful and…
This website uses cookies.