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.
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.
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.
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.
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.
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 tokensUI
Auth middlewareAPI
Deploy pipelineCI
Database migrationsDB
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
.