Build interactive sortable lists with drag-and-drop
Learn how to create draggable list items that can be reordered using hyperscript's
drag-and-drop events. Perfect for task lists, priority queues, and custom ordering!
draggabledragstartdragoverdrop
Interactive Task List
Drag items to reorder them, or use the controls to manage the list:
☰1
Complete project proposal
Draft and submit the Q4 project proposal
☰2
Review team feedback
Go through all feedback from the sprint retrospective
☰3
Update documentation
Update API docs with new endpoints and examples
☰4
Schedule team meeting
Find a time slot that works for everyone next week
☰5
Deploy to staging
Push latest changes to staging environment for testing
<!-- Visual insertion marker -->
<div id="insert-marker"></div>
<!-- All drag handlers on the container, not each item -->
<ul id="task-list"_="on dragstart
set item to target.closest('.sortable-item')
if item is not null then
add .dragging to item
end
set the event.dataTransfer.effectAllowed to 'move'
on dragend
set dragged to me.querySelector('.dragging')
if dragged is not null then remove .dragging from dragged end
add { opacity: 0; } to #insert-marker
on dragover
halt the event
set dropTarget to target.closest('.sortable-item')
if dropTarget is null then exit end
if dropTarget matches .dragging then exit end
-- Position insertion marker
set box to dropTarget.getBoundingClientRect()
set midpoint to box.top + box.height / 2
if event.clientY < midpoint then
set markerTop to box.top
else
set markerTop to box.bottom
end
add { top: ${markerTop}px; left: ${box.left}px;
width: ${box.width}px; opacity: 1; } to #insert-marker
on drop
halt the event
add { opacity: 0; } to #insert-marker
set dropTarget to target.closest('.sortable-item')
set draggedItem to me.querySelector('.dragging')
if dropTarget is null or draggedItem is null then exit end
set box to dropTarget.getBoundingClientRect()
if event.clientY < box.top + box.height / 2 then
call me.insertBefore(draggedItem, dropTarget)
else
call me.insertBefore(draggedItem, dropTarget.nextSibling)
end">
<!-- Items are simple - no individual handlers needed -->
<li class="sortable-item"draggable="true">
<span class="drag-handle">☰</span>
<div class="item-content">Task content</div>
</li>
</ul>
<button _="on click
call #task-list.children.reverse()
for item in #task-list.children
call #task-list.appendChild(item)
end">
Reverse
</button>
How it works:
draggable="true" - Makes element draggable
on dragstart - Fired when drag begins, adds visual feedback
on dragover - Fired when dragging over element, positions marker
on drop - Fired when dropped, reorders the items
halt the event - Prevents default browser behavior
insertBefore() - Repositions elements in the DOM
add { ... } to #marker - CSS template literals for dynamic styling
Key Concepts:
Event Delegation: All handlers on container, not each item (DRY principle)
Visual Marker: Fixed-position line shows precise drop location
CSS Template Literals:add { top: ${y}px; } for dynamic styles
Drag Events: dragstart, dragend, dragover, dragleave, drop
Position Detection: Use event.clientY and getBoundingClientRect()
DOM Manipulation: insertBefore() to reorder elements
Drag Event Sequence:
dragstart: User starts dragging an item
dragover: Dragged item moves over a drop target
dragleave: Dragged item leaves a drop target
drop: User releases the item over a drop target
dragend: Drag operation completes (success or cancel)
Advanced Patterns:
<!-- Drag between multiple lists -->
<ul class="droppable-list"_="on drop
halt the event
get first .dragging
if result exists
put result at end of me
end">
</ul>
<!-- Save order to localStorage -->
<button _="on click
set order to []
for item in .sortable-item
call order.push(item.id)
end
call localStorage.setItem('order', JSON.stringify(order))">
Save Order
</button>
<!-- Restore from localStorage -->
<div _="on load
set saved to localStorage.getItem('order')
if saved
set order to JSON.parse(saved)
for id in order
get #{id}
if result then put result at end of #list end
end
end">
</div>
<!-- Drag handles only -->
<li class="sortable-item">
<span class="drag-handle"draggable="true"_="on dragstart
set the dragTarget to closest .sortable-item
add .dragging to dragTarget">
☰
</span>
<div>Content (not draggable)</div>
</li>
Common Use Cases:
Task Lists: Prioritize tasks by dragging to reorder
Kanban Boards: Drag cards between columns
File Managers: Organize files and folders
Playlist Editors: Reorder songs in a playlist
Form Builders: Arrange form fields
Dashboard Widgets: Customize widget layout
Accessibility Considerations:
Add role="list" and role="listitem" for screen readers
Provide keyboard alternatives (arrow keys + modifier)