# Drag to Reorder

<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<div id="demo-content" class="not-prose demo-container flex justify-center"></div>

## Basic usage

This pattern integrates the [Sortable.js](https://sortablejs.github.io/Sortable/) 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.

```html
<form class="sortable" hx-post="/items" hx-trigger="end">
  <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-post`](https://four.htmx.org/reference/attributes/hx-post) sends the new order to `/items`.
- [`hx-trigger`](https://four.htmx.org/reference/attributes/hx-trigger)=`"end"` fires when Sortable.js finishes a drag (the `end` event bubbles up to the form).
- Each item has a hidden input so the server receives the ids in their new order.

On the server, read the `item` parameter (which arrives as an ordered list) and respond with the updated list HTML.

## Notes

### Sortable.js initialization

Initialize Sortable on your containers using the `htmx:load` event so it works after htmx swaps in new content.

```js
document.addEventListener("htmx:load", (e) => {
    const sortables = e.detail.elt.querySelectorAll(".sortable");
    for (const sortable of sortables) {
      const sortableInstance = new Sortable(sortable, {
          animation: 150,
          ghostClass: "sortable-ghost",

          // Make the `.htmx-indicator` unsortable
          filter: ".htmx-indicator",
          onMove: (evt) =>
            evt.related.className.indexOf("htmx-indicator") === -1,

          // Disable sorting until the server responds
          onEnd: function (evt) {
            this.option("disabled", true);
          }
      });

      // Re-enable sorting after the swap completes
      sortable.addEventListener("htmx:afterSwap", () => {
        sortableInstance.option("disabled", false);
      });
    }
});
```

Note that `onEnd` uses a regular `function` (not an arrow function) because it needs `this` to refer to the Sortable instance. Sorting is disabled during the request and re-enabled on `htmx:afterSwap` to prevent the user from reordering while the server is processing.