Tutorials
Retool Custom Drag-and-Drop Component: How to Build It
If you've been trying to build a Retool custom drag-and-drop component, you already know the problem: Retool's built-in component library doesn't include native drag-and-drop support. That means kanban-style boards, priority queues, order status pipelines, and any UI that lets users rearrange items visually requires a custom component. This guide walks you through how to build one, what libraries to use, and how to wire it up so Retool can actually read the new order.
Why Retool Doesn't Have a Native Drag-and-Drop Component
Retool is built for speed — its pre-built components cover the most common internal tool patterns. But drag-and-drop interaction is complex to implement generically because the data model varies wildly: you might be reordering rows in a list, moving cards between status columns, or nesting items in a tree. Rather than shipping a one-size-fits-all solution, Retool exposes a Custom Component API that lets you embed any JavaScript UI library and pass data in and out using model and modelUpdate. That's the hook you'll use.
What You Can Build with a Retool Drag-and-Drop Component
- Kanban boards where cards move between status columns (e.g. New → In Progress → Done)
- Order management UIs where dispatchers drag orders into driver queues
- Priority lists where users manually rank items before a bulk action
- Scheduling tools where time slots are filled by dragging tasks
These are all high-value internal tool patterns that show up constantly in ops, logistics, and support tooling — exactly the kind of thing Retool shops are asked to build every week.
How to Build a Custom Drag-and-Drop Component in Retool
Here's the step-by-step process. You'll use a Custom Component block and a lightweight JavaScript drag-and-drop library (SortableJS is the recommended choice — it's small, has no framework dependency, and works cleanly inside Retool's iframe sandbox).
- Step 1 — Add a Custom Component block. In your Retool canvas, drag in a
Custom Component. This creates an iframe where you can write raw HTML, CSS, and JavaScript. - Step 2 — Load SortableJS from a CDN. Inside the Custom Component's HTML panel, add the SortableJS script tag:
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>. This keeps your bundle small and avoids any build step. - Step 3 — Render your list from
model.items. Usewindow.Retool.subscribe(model => { ... })to receive data from Retool. Mapmodel.itemsto a rendered<ul>or set of cards inside the component. Each item should have a stableidfield — this is what you'll send back after a reorder. - Step 4 — Initialize SortableJS on your list element. Call
new Sortable(listEl, { animation: 150, onEnd: handleDrop }). TheonEndcallback fires after every drop with the old and new index. - Step 5 — Emit the new order back to Retool. Inside your
handleDropfunction, read the current DOM order, map it back to your item IDs, then callwindow.Retool.modelUpdate({ orderedIds: newOrder }). Retool will exposeorderedIdsas a property you can reference from any query or component in your app. - Step 6 — Trigger a write-back query. Add a Retool query (e.g.
updateItemOrder) that runs onmodelUpdateand writes the new order to your database. For a status-column kanban, include the target column ID in the payload so you know both the new order and the new status.
Passing Data Into and Out of the Component
The model object is your two-way data bridge. In the Model tab of your Custom Component, define the initial shape — something like { "items": [], "columns": [] }. You populate items by referencing a Retool query result: {{ getOrders.data }}. After a drag, modelUpdate pushes new state back into Retool's reactive graph, so any component or query watching customComponent1.model.orderedIds will update immediately.
One thing to watch: always use stable IDs, not array indexes, as your sort keys. If you rely on index position and the query refreshes mid-session, your order mapping will break silently.
Styling the Drag-and-Drop UI to Match Your App
Because the Custom Component renders inside an iframe, you have full CSS control with zero bleed from Retool's global styles. You can match your company's design system exactly — use CSS variables for colors, add hover states, show a drag handle icon (a common pattern is a ⠿ gripper glyph on the left of each card), and add a subtle drop-zone highlight using SortableJS's ghostClass and chosenClass options.
Common Issues When Building This
- The component doesn't receive updated data after a query refresh. Make sure you're re-rendering the list inside the
window.Retool.subscribecallback every time the model changes, not just on initial load. - Drag stops working after a re-render. If you're rebuilding the DOM on every model update, you need to re-initialize SortableJS on the new list element. Destroy the old instance first with
sortableInstance.destroy(). - modelUpdate fires but Retool queries don't trigger. Check that your query's trigger is set to
Event handler → Custom Component model change, not manual-only.
When to Use a Pre-Built Custom Component vs. Building Your Own
If you need something production-ready fast, community-shared custom components (like the one from the Retool community thread that inspired this post) can save you hours. They typically come pre-wired with model bindings and sensible defaults. That said, rolling your own gives you full control over the data schema, styling, and interaction model — worth it if this component is central to your app's workflow. Either way, the architecture above is what's running under the hood.
Ready to build?
We scope, design, and ship your Retool app — fast.