Display Sortable

Sortable

Drag-and-drop list sorting powered by the native HTML5 Drag and Drop API – no external dependencies. Works on ul , ol , div containers, and tbody table rows. Auto-initializes from data-wf-sortable . Cross-list dragging, drag handles, nested sublists, and AJAX order posting are built in.

Basic

Add data-wf-sortable to any .wf-sortable container to enable drag-and-drop reordering of its direct children. Grab any item and drag it to a new position.

  • Dashboard overview
  • User profiles
  • Analytics report
  • Settings panel
  • Help center
HTML
<ul class="wf-sortable wf-sortable-list" data-wf-sortable>
  <li class="wf-sortable-item" data-id="1">
    <span class="wf-sortable-item-title">Dashboard overview</span>
  </li>
  <li class="wf-sortable-item" data-id="2">
    <span class="wf-sortable-item-title">User profiles</span>
  </li>
  <li class="wf-sortable-item" data-id="3">
    <span class="wf-sortable-item-title">Analytics report</span>
  </li>
</ul>

Drag Handle

Set data-wf-handle=".wf-sortable-handle" on the container to restrict dragging to the grip icon only. The wf-sortable-item layout provides icon, title, and meta-slots for rich list rows.

  • Inbox 12 unread
  • Starred 4 items
  • Recently Viewed Yesterday
  • Scheduled 3 events
  • Trash Empty
HTML
<ul class="wf-sortable wf-sortable-list" data-wf-sortable
    data-wf-handle=".wf-sortable-handle">
  <li class="wf-sortable-item" data-id="1">
    <span class="wf-sortable-handle" aria-hidden="true">⠿</span>
    <span class="wf-sortable-item-icon"><i class="icon inbox" aria-hidden="true"></i></span>
    <span class="wf-sortable-item-title">Inbox</span>
    <span class="wf-sortable-item-meta">12 unread</span>
  </li>
</ul>

Horizontal

Add wf-sortable--horizontal to the container and set data-wf-direction="horizontal" to sort items in a flex-wrap row.

  • Design
  • Frontend
  • Backend
  • DevOps
  • QA
  • Security
HTML
<ul class="wf-sortable wf-sortable--horizontal" data-wf-sortable
    data-wf-direction="horizontal">
  <li class="wf-sortable-item wf-sortable-item--compact" data-id="1">Design</li>
  <li class="wf-sortable-item wf-sortable-item--compact" data-id="2">Frontend</li>
  <li class="wf-sortable-item wf-sortable-item--compact" data-id="3">Backend</li>
</ul>

Board

Use data-wf-group on multiple containers to allow items to be dragged between lists. Wrap columns in wf-sortable-board and wf-sortable-column for a kanban-style layout.

Backlog
  • Research competitors
  • Update documentation
  • Write unit tests
In Progress
  • API integration
  • UI redesign
Done
  • Deploy v1.0
  • Fix login bug
HTML
<div class="wf-sortable-board">
  <div class="wf-sortable-column">
    <div class="wf-sortable-column-title">Backlog</div>
    <ul class="wf-sortable wf-sortable-list" data-wf-sortable data-wf-group="board">
      <li class="wf-sortable-item" data-id="b1">
        <span class="wf-sortable-item-title">Research competitors</span>
      </li>
    </ul>
  </div>
  <div class="wf-sortable-column">
    <div class="wf-sortable-column-title">In Progress</div>
    <ul class="wf-sortable wf-sortable-list" data-wf-sortable data-wf-group="board">
      <!-- items -->
    </ul>
  </div>
</div>

Nested

Each [data-wf-sortable] element auto-inits independently, so inner lists can be sorted separately from the outer list. Use wf-sortable-nested-item and wf-sortable-tree-row to compose a tree structure.

  • Project Alpha
    • Task 1
    • Task 2
    • Task 3
  • Project Beta
    • Task A
    • Task B
HTML
<ul class="wf-sortable wf-sortable-list" data-wf-sortable>
  <li class="wf-sortable-nested-item" data-id="alpha">
    <div class="wf-sortable-tree-row">
      <strong class="wf-sortable-item-title">Project Alpha</strong>
    </div>
    <ul class="wf-sortable wf-sortable-list" data-wf-sortable>
      <li class="wf-sortable-item" data-id="a1">
        <span class="wf-sortable-item-title">Task 1</span>
      </li>
      <li class="wf-sortable-item" data-id="a2">
        <span class="wf-sortable-item-title">Task 2</span>
      </li>
    </ul>
  </li>
</ul>

Table Rows

Apply wf-sortable and data-wf-sortable to a tbody to make table rows draggable. A wf-sortable-handle in a dedicated column is recommended.

Name Role Status
Alice Admin Active
Bob Editor Active
Carol Viewer Inactive
Dave Editor Active
HTML
<table class="wf-table">
  <thead>
    <tr>
      <th></th>
      <th>Name</th>
      <th>Role</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody class="wf-sortable" data-wf-sortable data-wf-handle=".wf-sortable-handle">
    <tr data-id="1">
      <td><span class="wf-sortable-handle" aria-hidden="true">⠿</span></td>
      <td>Alice</td>
      <td>Admin</td>
      <td>Active</td>
    </tr>
  </tbody>
</table>

Disabled

Add data-wf-disabled to the container to prevent sorting. The attribute can be added or removed at runtime to toggle the disabled state, or use the programmatic s.disable() / s.enable() API.

  • Item one (not draggable)
  • Item two (not draggable)
  • Item three (not draggable)
HTML
<!-- data-wf-disabled prevents drag-and-drop -->
<ul class="wf-sortable wf-sortable-list" data-wf-sortable data-wf-disabled>
  <li class="wf-sortable-item" data-id="1">
    <span class="wf-sortable-item-title">Item one</span>
  </li>
</ul>

AJAX sort posting

Add data-wf-sort-url and data-wf-sort-action to automatically POST the new order to a server endpoint after every drop. Follows the same contract as wf-ajax-action.js .

  • Design system tokens UI
  • Auth middleware API
  • Deploy pipeline CI
  • Database migrations DB
HTML
<!-- Minimal: POST after every drop -->
<ul class="wf-sortable wf-sortable-list"
    data-wf-sortable
    data-wf-sort-url="/api/action"
    data-wf-sort-action="reorderTasks">
    <li class="wf-sortable-item" data-id="1">Task A</li>
    <li class="wf-sortable-item" data-id="2">Task B</li>
</ul>

Data Attributes

Attribute Element Values Description
data-wf-sortable .wf-sortable - Required for auto-init. Presence alone triggers initialization on DOMContentLoaded .
data-wf-handle .wf-sortable CSS selector Restrict dragging to a child element matching this selector. Default: null (whole item draggable).
data-wf-group .wf-sortable string Group name for cross-list dragging. Containers sharing the same group name accept drops from each other. Default: null .
data-wf-animation .wf-sortable ms Transition duration in milliseconds. Set to 0 to disable. Default: 150 .
data-wf-ghost-class .wf-sortable string Class added to the source item while dragging. Default: wf-sortable-ghost .
data-wf-chosen-class .wf-sortable string Class added to the item on pointer-down. Default: wf-sortable-chosen .
data-wf-direction .wf-sortable "vertical" | "horizontal" Drag axis. Default: vertical .
data-wf-disabled .wf-sortable - Disables sorting on init. Can be added or removed at runtime to toggle the state.
data-wf-sort-url .wf-sortable URL POST endpoint for AJAX order saving after each drop. Omit disabling AJAX posting.
data-wf-sort-action .wf-sortable string Value sent as action in the POST body. Default: reorder .
data-wf-sort-attr .wf-sortable string Item attribute used as the identifier in POST and toArray() . Default: data-id .
data-wf-csrf-header .wf-sortable string CSRF header name. Reads value from <meta name="csrf-token"> . Default: X-CSRF-Token . Set to "" to disable.

Events & Public API

Events

Dispatched on the .wf-sortable root element and bubble to document .

Event detail Description
wf:sortable-start { item, from, oldIndex } Drag started.
wf:sortable-end { item, from, to, oldIndex, newIndex } Drag ended - fires after every drop or cancel.
wf:sortable-update { item, from, to, oldIndex, newIndex } Item reordered within the same list.
wf:sortable-add { item, from, to, newIndex } Item dropped into this list from another (cross-list).
wf:sortable-remove { item, from, to, oldIndex } Item dragged out of this list to another (cross-list).
wf:sortable-post-before { url, body, inst } Before AJAX POST. Cancelable via preventDefault() .
wf:sortable-post-success { data, inst } 2xx response received from the sort POST.
wf:sortable-post-error { error, inst } Network error or non-2xx response from the sort POST.
JavaScript
import { WojoSortable } from './src/js/components/wf-sortable.js'

// Auto-init runs on DOMContentLoaded - manual init:
const s = new WojoSortable(el, {
  handle:      null,                    // CSS selector for drag handle
  group:       null,                    // Group name for cross-list drag
  animation:   150,                     // Transition ms (0 = off)
  ghostClass:  'wf-sortable-ghost',
  chosenClass: 'wf-sortable-chosen',
  direction:   'vertical',              // 'vertical' | 'horizontal'
  disabled:    false,
}).init()

// Instance accessed from the element after init:
const s = el._wfSortable

// Public methods
s.toArray('data-id')        // → string[]  Current item order
s.sort(['3', '1', '2'])     // Reorder items by data-id values
s.enable()                  // Re-enable after disable()
s.disable()                 // Disable sorting
s.destroy()                 // Remove all listeners and clean up

// Listen for reorder
el.addEventListener('wf:sortable-update', e => {
  const { item, from, to, oldIndex, newIndex } = e.detail
  console.log(`Moved from ${oldIndex} to ${newIndex}`)
  console.log('New order:', e.target._wfSortable.toArray())
})

// AJAX - POST new order after every drop:
// <ul class="wf-sortable" data-wf-sortable
//     data-wf-sort-url="/api/action"
//     data-wf-sort-action="reorderTasks">
// POST body: action, ids (comma-separated), item_id, old_index, new_index

Class Reference

Class Type Description
wf-sortable Base Sortable container. Apply to ul , ol , div , or tbody . Add data-wf-sortable to auto-init.
wf-sortable-handle Interactive Drag a handle within an item. Styled with a grab cursor. When data-wf-handle is set on the container, only this element initiates a drag.
wf-sortable-ghost State Applied to the source item while dragging - reduces opacity and scale. Default ghost class; override with data-wf-ghost-class .
wf-sortable-chosen State Applied to the item on pointer-down - elevates with a drop shadow. Default chosen class; override with data-wf-chosen-class .
wf-sortable-placeholder State Drop-target indicator inserted during drag. Dashed border with animated entry. Added and removed by JS – do not apply manually.
wf-sortable--horizontal Modifier Flex-wrap horizontal layout for the container. Use with data-wf-direction="horizontal" .
wf-sortable-list Layout Flex-column wrapper with a gap for card-style sortable items. Often combined with wf-sortable on the same element.
wf-sortable-item Layout Item card with border, shadow, and flex row layout. Variant: wf-sortable-item--compact for reduced padding.
wf-sortable-item-title Layout Flex-1 text region within a wf-sortable-item .
wf-sortable-item-icon Layout Icon slot within a wf-sortable-item . Muted color, 1rem size.
wf-sortable-item-meta Layout Muted meta-text within a wf-sortable-item . Small, non-wrapping.
wf-sortable-board Layout Auto-grid wrapper for multi-column cross-list layouts (kanban boards).
wf-sortable-column Layout Individual column within a wf-sortable-board .
wf-sortable-column-title Layout Column header label within a wf-sortable-column .
wf-sortable-nested-item Layout Item that contains a nested child sortable. Adds an indent guideline on the left.
wf-sortable-tree-row Layout Flex row header within a wf-sortable-nested-item .
Show More