@arc-lang/arc-drag-and-drop
v0.1.1
Published
CSS-first drag-and-drop: sortable lists, kanban boards, file drop zones. Zero dependencies. ~1.5KB JS.
Maintainers
Readme
arc-drag-and-drop
CSS-first drag-and-drop: sortable lists, kanban boards, and file drop zones. CSS drives all visual states. The optional JS (~1.5KB total) handles DOM reordering and pointer tracking using the Pointer Events API — touch and mouse work identically.
Part of Arc — the zero-dependency web framework.
vs. alternatives: | Library | Size | Touch | Framework | |---|---|---|---| | SortableJS | ~45KB | ✓ | any | | dnd-kit | ~30KB | ✓ | React only | | interact.js | ~70KB | ✓ | any | | arc-drag-and-drop | ~1.5KB | ✓ | any |
Install
npm install arc-drag-and-dropCDN:
<link rel="stylesheet" href="https://unpkg.com/arc-drag-and-drop/index.css">Components
Sortable
Reorderable list. JS uses Pointer Events (mouse + touch) with rAF-throttled pointermove and GPU-composited clone positioning.
<ul class="sortable">
<li class="sortable__item">Task one</li>
<li class="sortable__item">Task two</li>
<li class="sortable__item">Task three</li>
</ul>
<script src="https://unpkg.com/arc-drag-and-drop/js/sortable.js"></script>With drag handle — only the handle initiates a drag:
<ul class="sortable" data-handle=".drag-handle">
<li class="sortable__item">
<span class="drag-handle">⠿</span>
Item content
</li>
</ul>Horizontal:
<div class="sortable sortable--horizontal">
<div class="sortable__item">A</div>
<div class="sortable__item">B</div>
</div>Disabled item:
<li class="sortable__item" data-disabled>Cannot be moved</li>| CSS property | Default | Description |
|---|---|---|
| --sortable-gap | 8px | Gap between items |
| --sortable-radius | 8px | Item border radius |
| --sortable-padding | 12px 16px | Item padding |
| --sortable-item-bg | #fff | Item background |
| --sortable-item-border | #e2e8f0 | Item border color |
| --sortable-drag-rotate | 2deg | Clone tilt while dragging |
| --sortable-placeholder-bg | rgba(89,86,240,0.06) | Placeholder fill |
| --sortable-placeholder-border | rgba(89,86,240,0.3) | Placeholder border |
| --sortable-handle-color | #94a3b8 | Handle icon color |
| --sortable-handle-color-hover | #475569 | Handle icon hover color |
Custom event:
document.querySelector('.sortable').addEventListener('dnd:sorted', function (e) {
console.log(e.detail)
// { item, fromIndex, toIndex, fromContainer, toContainer }
})Kanban
Multi-column board. Cards are .sortable__item inside .kanban__column containers. Add data-group to enable cross-column drag.
<div class="kanban">
<div class="kanban__column sortable" data-group="board" data-handle=".drag-handle">
<div class="kanban__column-header">To do</div>
<div class="sortable__item">
<span class="drag-handle">⠿</span>
Write docs
</div>
</div>
<div class="kanban__column sortable" data-group="board" data-handle=".drag-handle">
<div class="kanban__column-header">In progress</div>
<div class="sortable__item">
<span class="drag-handle">⠿</span>
Build feature
</div>
</div>
</div>
<script src="https://unpkg.com/arc-drag-and-drop/js/sortable.js"></script>| CSS property | Default | Description |
|---|---|---|
| --kanban-gap | 16px | Gap between columns |
| --kanban-column-width | 280px | Column width |
| --kanban-column-bg | #f1f5f9 | Column background |
| --kanban-radius | 12px | Column border radius |
| --kanban-column-min-height | 120px | Minimum column height |
| --kanban-over-bg | rgba(89,86,240,0.08) | Column highlight on drag-over |
| --kanban-over-border | rgba(89,86,240,0.35) | Column outline on drag-over |
Data attributes:
| Attribute | Value | Description |
|---|---|---|
| data-group | "board" | Containers with the same group accept cross-container drops |
| data-handle | ".drag-handle" | CSS selector that initiates drag |
| data-axis | "x" or "y" | Lock drag to one axis |
Dropzone
File drop zone. Uses HTML5 drag events (required for OS file drops). Fires dnd:drop on both file drop and click-to-browse.
<div class="dropzone" data-accept="image/*">
<div class="dropzone__icon">📁</div>
<div class="dropzone__label">Drop images here</div>
<div class="dropzone__hint">or click to browse</div>
<input type="file" multiple accept="image/*">
</div>
<script src="https://unpkg.com/arc-drag-and-drop/js/dropzone.js"></script>data-accept takes a comma-separated list of MIME types or extensions:
image/*— any image.pdf,.docx— specific extensionstext/plain— exact MIME
| CSS property | Default | Description |
|---|---|---|
| --dropzone-padding | 48px 32px | Inner padding |
| --dropzone-radius | 12px | Border radius |
| --dropzone-border | #cbd5e1 | Default border color |
| --dropzone-bg | #f8fafc | Default background |
| --dropzone-over-border | #5956f0 | Drag-over border color |
| --dropzone-over-bg | rgba(89,86,240,0.06) | Drag-over background |
| --dropzone-success-border | #22c55e | Accepted drop border |
| --dropzone-success-bg | rgba(34,197,94,0.06) | Accepted drop background |
| --dropzone-error-border | #ef4444 | Rejected drop border |
| --dropzone-error-bg | rgba(239,68,68,0.06) | Rejected drop background |
Custom event:
document.querySelector('.dropzone').addEventListener('dnd:drop', function (e) {
console.log(e.detail.files) // Array of File objects
})Tree-shaking
Import only what you need:
<link rel="stylesheet" href="https://unpkg.com/arc-drag-and-drop/src/base.css">
<link rel="stylesheet" href="https://unpkg.com/arc-drag-and-drop/src/sortable.css">| File | Size (approx) |
|------|---------------|
| src/base.css | ~0.5 KB |
| src/sortable.css | ~1.5 KB |
| src/dropzone.css | ~1 KB |
| src/kanban.css | ~1.5 KB |
| js/sortable.js | ~1.1 KB |
| js/dropzone.js | ~0.5 KB |
| Full bundle | ~4.5 KB CSS + ~1.6 KB JS |
Performance
| Technique | Effect |
|---|---|
| rAF-throttled pointermove | Layout reads happen at most once per frame |
| translate3d(x, y, 0) on clone | GPU-composited movement, no reflow |
| Event delegation (1 listener/container) | O(1) regardless of item count |
| Placeholder only moves when position changes | Minimizes DOM mutations |
| will-change: transform added dynamically | No wasted GPU layers when idle |
| pointer-events: none on clone | elementFromPoint sees through it correctly |
Browser support
| Feature | Chrome | Firefox | Safari | Edge |
|---------|--------|---------|--------|------|
| Sortable (Pointer Events) | 55+ | 59+ | 13+ | 79+ |
| Kanban (cross-container) | 55+ | 59+ | 13+ | 79+ |
| Dropzone (HTML5 DnD + File API) | 21+ | 50+ | 10.1+ | 12+ |
| touch-action: none | 36+ | 52+ | 13+ | 79+ |
Accessibility
prefers-reduced-motion:--sortable-drag-rotateis forced to0deg— no tilt on the drag clone- Keyboard: sortable items are not keyboard-reorderable by default — pair with a server-side or form-based fallback for full a11y
- Screen readers: drag clone gets no ARIA attributes (it's
pointer-events: noneand ephemeral); the item is moved in the DOM on drop
License
MIT © Arc contributors
