How to Implement Drag and Drop in Svelte
Drag and drop feels simple until you try to build it. The browser gives you a native API, but it comes with real limitations: no smooth animations, inconsistent touch support, and unpredictable behavior across browsers. If you’ve ever watched items snap awkwardly into place on drop, you know the problem.
This guide covers two practical approaches to implement drag and drop in Svelte — using the native HTML5 API and using a library — so you can pick the right tool for what you’re actually building.
Key Takeaways
- The native HTML5 Drag and Drop API works for simple list reordering with zero dependencies, but lacks smooth animations, touch support, and consistent cross-browser behavior.
- Svelte 5 runes (
$state()) and theondragstartattribute syntax simplify reactive drag and drop compared to Svelte 3/4. - For animated, multi-list, touch-friendly, or accessible drag and drop,
svelte-dnd-actionis a practical library choice. - Drag events are client-only — in SvelteKit with SSR, guard drag logic with
onMountor abrowsercheck.
Understanding the Two Approaches
Before writing any code, it helps to understand what you’re choosing between.
Native HTML5 Drag and Drop API uses browser-built events: dragstart, dragover, dragenter, dragleave, and drop. It requires zero dependencies and works well for simple use cases. The tradeoff is that animations require manual work, touch support is inconsistent across devices, and visual feedback during dragging is limited. You can read more about the API in the MDN Drag and Drop documentation.
Library-based solutions like svelte-dnd-action or Neodrag handle the hard parts — smooth FLIP animations, touch support, and accessible interactions — out of the box. They add a small bundle cost but save significant implementation time for anything beyond a basic sortable list.
Implementing Drag and Drop in Svelte 5 with the Native API
Here’s a clean single-list reorder example using Svelte 5 runes syntax:
<script>
let items = $state(['Svelte', 'SvelteKit', 'Vite', 'TypeScript']);
let dragIndex = $state(null);
function handleDragStart(event, index) {
dragIndex = index;
event.dataTransfer.effectAllowed = 'move';
}
function handleDragOver(event, index) {
event.preventDefault();
if (dragIndex === null || dragIndex === index) return;
const updated = [...items];
const [moved] = updated.splice(dragIndex, 1);
updated.splice(index, 0, moved);
items = updated;
dragIndex = index;
}
function handleDragEnd() {
dragIndex = null;
}
</script>
<ul>
{#each items as item, index (item)}
<li
draggable="true"
class:dragging={dragIndex === index}
ondragstart={(e) => handleDragStart(e, index)}
ondragover={(e) => handleDragOver(e, index)}
ondragend={handleDragEnd}
>
{item}
</li>
{/each}
</ul>
<style>
li {
padding: 10px 16px;
margin: 6px 0;
background: #f1f1f1;
cursor: grab;
list-style: none;
border-radius: 4px;
}
.dragging {
opacity: 0.4;
}
</style>
A few things worth noting about this Svelte 5 pattern:
$state()replaces the old reactive variable declarations from Svelte 3/4.- Svelte 5 also supports the
ondragstartattribute syntax in addition to the traditionalon:dragstartevent directive. - The list updates during drag (on
dragover), not just on drop — this gives users real-time visual feedback.
SvelteKit note: Drag events are client-only. If you’re using SvelteKit with SSR, wrap any drag-related logic in onMount or guard it with a browser check from $app/environment.
Discover how at OpenReplay.com.
When to Use svelte-dnd-action Instead
The native approach works for simple list reorder scenarios. But once you need any of the following, reach for svelte-dnd-action:
- Smooth FLIP animations between list positions
- Multi-list drag and drop (Kanban-style boards)
- Touch and mobile support without extra code
- Accessible keyboard interactions built in
The svelte-dnd-action pattern is straightforward — you apply a use:dndzone action to your container, pass your items array, and handle consider and finalize events to update state. Each item needs a unique id property. Pair it with Svelte’s built-in flip animation and you get production-quality drag interactions in under 20 lines.
| Need | Use |
|---|---|
| Simple list reorder, no animations | Native API |
| Smooth animations, multi-list, touch | svelte-dnd-action |
| Free-form element dragging | Neodrag |
Conclusion
For basic Svelte drag and drop list reordering, the native browser API with Svelte 5 runes gets you there with no dependencies. For anything more complex — animated Kanban boards, touch support, or accessible interactions — svelte-dnd-action is the practical choice. Start with the native approach to understand the mechanics, then upgrade to a library when your requirements demand it.
FAQs
Not reliably. Support for drag events on touch devices is inconsistent across mobile browsers. To support mobile properly, you either need a polyfill like mobile-drag-drop or a library such as svelte-dnd-action that handles touch interactions natively.
Yes. svelte-dnd-action works with Svelte 5. You declare your items array with $state() and update it inside the consider and finalize event handlers. The use:dndzone directive remains the same. Just make sure each item in your array has a unique id property for the library to track elements correctly.
The browser generates a default ghost image from the dragged element's appearance. You have limited control over this with the native API. You can customize it using event.dataTransfer.setDragImage() to provide a custom element or canvas snapshot, but for full visual control during drag, a library like svelte-dnd-action or Neodrag is a better fit.
Multi-list drag and drop with the native API requires tracking source and target containers manually, which gets complex fast. svelte-dnd-action simplifies this by letting you apply use:dndzone to each list container and sharing the same item type across zones. Items move between lists automatically when dragged across containers.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.