PhoenixKitWeb.Components.Core.DraggableList (phoenix_kit v1.7.117)

Copy Markdown View Source

A reusable drag-and-drop sortable component supporting both grid and list layouts.

Uses SortableJS (auto-loaded from CDN) to enable drag-and-drop reordering of items. The component sends a LiveView event when items are reordered.

Design Philosophy

This component provides minimal opinionated styling - it handles drag-drop behavior while you control the appearance through classes and slot content.

Usage - Grid Layout (default)

Perfect for image galleries, card grids, etc:

<.draggable_list
  id="post-images"
  items={@images}
  on_reorder="reorder_images"
  cols={4}
>
  <:item :let={img}>
    <img src={img.url} class="w-full aspect-square object-cover rounded" />
  </:item>
  <:add_button>
    <button phx-click="add_image" class="btn">Add</button>
  </:add_button>
</.draggable_list>

Usage - List Layout

Perfect for column selectors, ordered lists, etc:

<.draggable_list
  id="table-columns"
  items={@columns}
  on_reorder="reorder_columns"
  layout={:list}
  item_class="flex items-center p-3 bg-base-100 border rounded-lg hover:bg-base-200"
>
  <:item :let={col}>
    <div class="mr-3 text-base-content/40">
      <.icon name="hero-bars-3" class="w-5 h-5" />
    </div>
    <span class="flex-1 font-medium">{col.label}</span>
    <button phx-click="remove_column" phx-value-id={col.id} class="btn btn-ghost btn-xs">
      <.icon name="hero-x-mark" class="w-4 h-4" />
    </button>
  </:item>
</.draggable_list>

Event Handler

The on_reorder event receives %{"ordered_ids" => [id1, id2, ...]} with the new order:

def handle_event("reorder_items", %{"ordered_ids" => ordered_ids}, socket) do
  # ordered_ids is a list of item IDs in the new order
  {:noreply, socket}
end

Inside a LiveComponent

The on_reorder event is pushed by the SortableGrid JS hook. By default the hook uses pushEvent, which delivers the event to the host LiveView. When <.draggable_list> is rendered inside a LiveComponent that handles the event itself, pass target — a CSS selector for the component's root element — so the hook routes via pushEventTo and the event reaches the component:

<.draggable_list id="my-gallery-grid" items={@items}
  on_reorder="reorder_images" target={"##{@id}"}>

Without target, a LiveComponent's handle_event("reorder_…") never fires and the event lands on the host LiveView instead.

Summary

Functions

draggable_list(assigns)

Attributes

  • id (:string) (required) - Unique ID for the container.
  • items (:list) (required) - List of items to display.
  • item_id (:any) - Function to extract ID from item, defaults to &(&1.id). Defaults to nil.
  • on_reorder (:string) (required) - Event name to send on reorder.
  • layout (:atom) - Layout mode. Defaults to :grid. Must be one of :grid, or :list.
  • cols (:integer) - Number of grid columns (only for layout={:grid}). Defaults to 4.
  • gap (:string) - Gap between items (Tailwind class). Defaults to "gap-2".
  • class (:string) - Additional CSS classes for the container. Defaults to "".
  • item_class (:string) - Additional CSS classes for each item wrapper. Defaults to "".
  • hide_source (:boolean) - Hide source element on drag start. Defaults to false.
  • sortable_handle (:string) - Optional CSS selector (e.g. ".pk-drag-handle") that restricts drag initiation to elements matching the selector inside each item. When set, the item wrapper no longer carries the cursor-grab styling — the caller is responsible for rendering the handle. Mirrors the workspace's <.table_default> :on_reorder + .pk-drag-handle convention. Defaults to nil.
  • target (:string) - Optional CSS selector for the LiveComponent root that should receive the on_reorder event (e.g. "#my-gallery"). When set, the SortableGrid hook uses pushEventTo instead of pushEvent. Required when <.draggable_list> is rendered inside a LiveComponent that handles the reorder event itself. Defaults to nil.
  • draggable (:boolean) - When false, the container renders without the SortableGrid hook and items skip the grab-cursor styling — useful when the list is too short to reorder (length <= 1). data-id is still emitted on each item so click-to-select handlers and test selectors work in both modes. Defaults to true.

Slots

  • item (required) - Slot to render each item, receives the item as let.
  • add_button - Optional slot for add button at end of container.