Dragit Usage Guide
New to Dragit?
Start with the Overview for a quick introduction and installation, then come back here for in-depth usage patterns.
Drop Zone
createDropZone attaches drag-and-drop behaviour to any DOM element. It handles the quirks of the HTML Drag & Drop API — most importantly, hover state is tracked with a counter so it never fires spuriously when the cursor moves over a child element.
Basic usage
const zone = createDropZone({
element: document.getElementById('dropzone')!,
onDrop: (files, event) => {
// files is always a non-empty File[]
uploadFiles(files);
},
});Accept filtering
Pass an accept array to restrict which files are accepted. Patterns follow the format used by <input type="file" accept="...">:
| Pattern | Matches |
|---|---|
'image/png' | Exact MIME type |
'image/*' | Any image MIME type |
'.pdf' | File extension (case-insensitive) |
const zone = createDropZone({
element: dropEl,
accept: ['image/*', '.pdf', 'application/json'],
onDrop: (files) => {
// Only accepted files arrive here
},
onDropRejected: (files) => {
showToast(`${files.length} file(s) not accepted`);
},
});Files are pre-validated during drag via dataTransfer.items (MIME types and wildcards) — the cursor shows a none drop effect before the user even releases. Extension patterns (.pdf) can only be confirmed at drop time since DataTransferItem does not expose filenames, so they receive optimistic hover treatment.
Hover state
onHoverChange is the simplest way to react to drag-over state. It receives true when the drag enters the zone and false when it leaves or completes.
const zone = createDropZone({
element: dropEl,
onHoverChange: (hovered) => {
dropEl.classList.toggle('drag-over', hovered);
},
});For more control, use onDragEnter and onDragLeave directly:
const zone = createDropZone({
element: dropEl,
onDragEnter: (event) => {
/* drag has entered */
},
onDragLeave: (event) => {
/* drag has left */
},
onDragOver: (event) => {
/* fires every frame while hovering */
},
});Reading the current state imperatively:
console.log(zone.hovered); // boolean — true while dragging overdropEffect
Control the cursor feedback shown to the user while dragging over the zone. Defaults to 'copy'.
const zone = createDropZone({
element: dropEl,
dropEffect: 'move', // 'copy' | 'move' | 'link' | 'none'
onDrop: (files) => {
/* ... */
},
});TIP
onDragOver can override dropEffect dynamically per-frame if you need conditional feedback (e.g. 'move' when Alt is held, 'copy' otherwise).
Disabled state
Pass a function to disabled. When it returns true, all drag events are silently ignored and hover state will not change.
const zone = createDropZone({
element: dropEl,
disabled: () => props.readOnly,
onDrop: (files) => {
/* ... */
},
});The function form lets reactive frameworks pass a derived or computed value:
// Vue 3 / signals-based frameworks
disabled: () => isReadOnly.value;Cleanup
Call destroy() to remove all event listeners and reset internal state:
zone.destroy();Or use the using keyword (TypeScript 5.2+, requires "lib": ["ESNext.Disposable"]):
{
using zone = createDropZone({ element: dropEl, onDrop: handleFiles });
// zone is active here
} // zone.destroy() is called automatically at block exitSortable
createSortable makes the direct children of a container element reorderable via drag. It uses event delegation — a single set of listeners on the container handles all children.
Setup
Every sortable item must carry a data-sort-id attribute with a unique, stable identifier:
<ul id="task-list">
<li data-sort-id="task-1">Design</li>
<li data-sort-id="task-2">Develop</li>
<li data-sort-id="task-3">Review</li>
</ul>const sortable = createSortable({
container: document.getElementById('task-list')!,
onReorder: (ids) => {
// ids is the new ordered array of data-sort-id values
// Only called when the order actually changed
saveTaskOrder(ids); // e.g. ['task-2', 'task-1', 'task-3']
},
});createSortable automatically:
- Sets
draggable="true"on all[data-sort-id]children - Sets
role="listitem"on each item androle="list"on the container - Removes both attributes on
destroy()
Drag handles
By default the entire item is draggable. Pass a handle selector to restrict dragging to a specific child element:
<ul id="task-list">
<li data-sort-id="task-1">
<span class="drag-handle">⠿</span>
Design
</li>
<li data-sort-id="task-2">
<span class="drag-handle">⠿</span>
Develop
</li>
</ul>const sortable = createSortable({
container: listEl,
handle: '.drag-handle',
onReorder: (ids) => {
saveOrder(ids);
},
});Lifecycle hooks
const sortable = createSortable({
container: listEl,
onDragStart: (id, event) => {
// id — the data-sort-id of the item being dragged
listEl.classList.add('sorting');
},
onDragEnd: (event) => {
listEl.classList.remove('sorting');
},
onReorder: (ids) => {
saveOrder(ids);
},
});Dynamic lists
When items are added to or removed from the container after createSortable is called, call refresh() to re-scan and update draggable/role on the current children:
// Add a new item to the DOM
const newItem = document.createElement('li');
newItem.dataset.sortId = 'task-4';
newItem.textContent = 'Deploy';
listEl.appendChild(newItem);
// Sync dragit state
sortable.refresh();Disabled state
const sortable = createSortable({
container: listEl,
disabled: () => isLocked,
onReorder: (ids) => {
saveOrder(ids);
},
});When disabled, dragstart is blocked. If the sortable becomes disabled while a drag is in progress, onReorder will not fire at drag end.
Styling the placeholder
While an item is being dragged, dragit inserts a <div class="dragit-placeholder"> in the list to indicate the drop position. Only height is set inline (mirroring the dragged item). All visual styling is left to your CSS:
.dragit-placeholder {
background: var(--color-primary-50);
border: 2px dashed var(--color-primary-300);
border-radius: 4px;
box-sizing: border-box;
transition: opacity 150ms;
}The item being dragged receives a data-dragging attribute while in flight, which you can also style:
[data-dragging] {
opacity: 0.4;
}Cleanup
sortable.destroy();
// or:
using sortable = createSortable({ container: listEl, onReorder: saveOrder });destroy() removes all event listeners, strips draggable/role from all items, and cleans up any in-progress drag state (removes data-dragging attribute, removes the placeholder).