Dragit
Dragit is a zero-dependency drag-and-drop library for the Document Object Model (DOM). It provides two focused primitives: a drop zone for file drag-and-drop with MIME type filtering and a sortable list for reordering elements. Both wrap the HTML Drag and Drop API with reliable hover state, full lifecycle management, and using keyword support.
Installation
pnpm add @vielzeug/dragitnpm install @vielzeug/dragityarn add @vielzeug/dragitQuick Start
import { createDropZone, createSortable } from '@vielzeug/dragit';
// File drop zone
using zone = createDropZone({
element: document.getElementById('dropzone')!,
accept: ['image/*', '.pdf'],
onDrop: (files) => {
uploadFiles(files);
},
onDropRejected: (files) => {
showError(`${files.length} file(s) not accepted`);
},
onHoverChange: (hovered) => {
document.getElementById('dropzone')!.classList.toggle('drag-over', hovered);
},
});
// Sortable list
using sortable = createSortable({
container: document.getElementById('list')!,
onReorder: (ids) => {
saveOrder(ids);
},
});Why Dragit?
The HTML5 Drag & Drop API requires careful counter tracking to avoid hover state flicker, has no MIME type pre-filtering, and provides no sortable list abstraction.
// Before — raw HTML5 Drag & Drop
let enterCount = 0;
dropzone.addEventListener('dragenter', () => {
enterCount++;
dropzone.classList.add('over');
});
dropzone.addEventListener('dragleave', () => {
if (--enterCount === 0) dropzone.classList.remove('over');
});
dropzone.addEventListener('dragover', (e) => e.preventDefault());
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
enterCount = 0;
const files = [...e.dataTransfer!.files];
if (!files.every((f) => f.type.startsWith('image/'))) return showError('Images only');
uploadFiles(files);
});
// After — Dragit
import { createDropZone } from '@vielzeug/dragit';
const zone = createDropZone({
element: dropzone,
accept: ['image/*'],
onDrop: (files) => uploadFiles(files),
onDropRejected: (files) => showError(`${files.length} file(s) not accepted`),
onHoverChange: (hovered) => dropzone.classList.toggle('over', hovered),
});| Feature | Dragit | SortableJS | dnd-kit |
|---|---|---|---|
| Bundle size | 1.3 KB | ~15 kB | ~30 kB |
| Framework agnostic | ✅ | ✅ | ✅ |
| MIME type filtering | ✅ Pre-validated | ❌ | ❌ |
| Counter-based hover | ✅ | ❌ | N/A |
| Sortable lists | ✅ | ✅ | ✅ |
| Drag handles | ✅ | ✅ | ✅ |
using support | ✅ | ❌ | ❌ |
| Zero dependencies | ✅ | ✅ | ❌ |
Use Dragit when you need reliable file drop zones with MIME filtering or sortable lists in a framework-agnostic environment.
Consider dnd-kit if you are building a React app and need complex multi-container drag interactions or accessibility-first sortable trees.
Features
- Counter-based hover state —
onHoverChangestays accurate when dragging over child elements; no spurious leave/enter flicker - MIME type pre-validation — queries
dataTransfer.itemsduring drag to setdropEffect='none'before the drop; confirmed againstFile.typeon drop - Flexible accept patterns — MIME types (
image/png), wildcards (image/*), and file extensions (.pdf) onDropRejected— separate callback for files that didn't matchaccept; enables rejection UX without extra filtering logic- Sortable lists — reorders DOM children with a placeholder indicator; fires
onReorderonly when the order actually changes - Drag handles — scope dragging to a child selector via
handle; whole item is draggable when omitted sortable.refresh()— re-syncsdraggableandroleattributes after adding or removing list items[Symbol.dispose]— both primitives support theusingkeyword for automatic cleanup- Reactive-friendly
disabled— pass() => signal.valueto integrate with any reactive framework - Zero dependencies — 1.3 KB gzipped, 0 dependencies
Compatibility
| Environment | Support |
|---|---|
| Browser | ✅ |
| Node.js | ❌ (DOM only) |
| SSR | ❌ (DOM only) |
| Deno | ❌ |
Prerequisites
- Browser runtime with HTML Drag and Drop API support.
- Render targets must be real DOM elements before calling
createDropZone()orcreateSortable(). - Provide accessible labels and keyboard alternatives for drag interactions in production UIs.