Basic usage
This pattern integrates the Sortable.js library with htmx to persist drag-and-drop reordering on the server.
On the client, wrap your items in a form that posts on the Sortable end event.
<form class="sortable" hx-post="/items" hx-trigger="end" hx-on:load="new Sortable(this, { animation: 150, ghostClass: 'sortable-ghost', filter: '.htmx-indicator', onMove: e => !e.related.classList.contains('htmx-indicator'), onEnd: function () { this.option('disabled', true) } })" hx-on::after:swap="Sortable.get(this).option('disabled', false)"> <div class="htmx-indicator">Updating...</div> <div><input type="hidden" name="item" value="1"/>Item 1</div> <div><input type="hidden" name="item" value="2"/>Item 2</div> <div><input type="hidden" name="item" value="3"/>Item 3</div> </form>
hx-postsends the new order to/items.hx-trigger="end"fires when Sortable finishes a drag. Note that theendevent bubbles up to the form.- Each item has a hidden input, so the server receives the ids in their new order.
- The
.htmx-indicatordiv shows while the request is in flight. htmx hides it again when the response completes. hx-on:loadbuilds the Sortable instance when htmx processes the form. Note that this is just for convenience, you could easily move this to a vanilla setup script.