Now.js Framework Documentation

Now.js Framework Documentation

Sortable

EN 04 Sep 2026 01:40

Sortable

Overview

Sortable is a component for drag-and-drop sorting in Now.js Framework. It supports lists, grids, and API integration.

When to use:

  • Need to reorder items with drag-drop
  • Need Kanban boards
  • Need sortable tables
  • Need auto-save position

Why use it:

  • ✅ Drag and drop reordering
  • ✅ Cross-container moving
  • ✅ Keyboard support (accessibility)
  • ✅ Touch device support
  • ✅ Auto-save API integration
  • ✅ Ghost element preview
  • ✅ Placeholder visualization

Basic Usage

HTML Declarative

<div data-component="sortable" data-sortable-draggable=".item">
  <div class="item" draggable="true">Item 1</div>
  <div class="item" draggable="true">Item 2</div>
  <div class="item" draggable="true">Item 3</div>
</div>

With Handle

<div data-component="sortable"
     data-sortable-draggable=".item"
     data-sortable-handle=".handle">
  <div class="item" draggable="true">
    <span class="handle">☰</span>
    <span>Item 1</span>
  </div>
  <div class="item" draggable="true">
    <span class="handle">☰</span>
    <span>Item 2</span>
  </div>
</div>

JavaScript API

const sortable = Sortable.create(container, {
  draggable: '.item',
  handle: '.handle',
  onEnd: (evt) => {
    console.log('Moved from', evt.oldIndex, 'to', evt.newIndex);
  }
});

Data Attributes

Attribute Description
data-draggable Selector for draggable items
data-handle Selector for drag handle
data-group Group name for cross-container
data-sortable-api API endpoint for auto-save
data-sortable-id-attr Attribute for item ID
data-sortable-update-field Field to update on move
data-sortable-method HTTP method used to save the order (PUT by default)
data-sortable-extra-data Extra data sent with the save request, as JSON
data-api-endpoint Fallback endpoint when data-sortable-api is not set
data-api-method Fallback method when data-sortable-method is not set
data-api-url URL sent along with the save payload
data-animation Swap animation duration in milliseconds (150)
data-ghost-class CSS class of the placeholder left while dragging (sortable-ghost)
data-chosen-class CSS class of the chosen item (sortable-chosen)
data-drag-class CSS class of the item being dragged (sortable-drag)

Options

Sortable.create(element, {
  // Selector for draggable items
  draggable: '.item',

  // Selector for drag handle (optional)
  handle: '.drag-handle',

  // Group name for cross-container sorting
  group: 'shared',

  // Ghost element class
  ghostClass: 'sortable-ghost',

  // Placeholder class
  placeholderClass: 'sortable-placeholder',

  // Animation duration (ms)
  animation: 150,

  // Callbacks
  onStart: (evt) => {},
  onEnd: (evt) => {},
  onChange: (evt) => {}
});

API Reference

Sortable.create(element, options)

Create sortable instance

Parameter Type Description
element HTMLElement Container element
options object Configuration options

Returns: Sortable - Instance

const sortable = Sortable.create(container, {
  draggable: '.card'
});

Sortable.getInstance(element)

Get instance from element

Parameter Type Description
element HTMLElement Container element

Returns: Sortable|null

instance.enable()

Enable sorting

instance.disable()

Disable sorting

instance.destroy()

Destroy instance

Events

Sortable events are DOM CustomEvents dispatched on the container element (they
bubble), so listen with element.addEventListener(), not EventManager.on().

Event When Triggered Detail
sortable:start Drag started {item, startIndex}
sortable:change Order changed during the drag {item, newIndex, oldIndex}
sortable:end Item dropped {item, newIndex, oldIndex, to, from}
sortable:select An item was selected or deselected {item, selected}
sortable:api-success The new order was saved through the API {item, response, payload}
sortable:api-error Saving the new order failed {item, error}
element.addEventListener('sortable:end', (e) => {
  console.log(`Moved from ${e.detail.oldIndex} to ${e.detail.newIndex}`);
});

Auto-Save API

Automatically save position on drop:

<div data-component="sortable"
     data-sortable-draggable=".task"
     data-sortable-api="/api/tasks/{id}/position"
     data-sortable-id-attr="data-id"
     data-sortable-update-field="position">
  <div class="task" data-id="1" draggable="true">Task 1</div>
  <div class="task" data-id="2" draggable="true">Task 2</div>
</div>

API Request sent:

PATCH /api/tasks/1/position
{ "position": 2 }

CSS Styling

/* Draggable items */
.item[draggable="true"] {
  cursor: grab;
}

.item[draggable="true"]:active {
  cursor: grabbing;
}

/* Ghost element (follows cursor) */
.sortable-ghost {
  opacity: 0.7;
  transform: scale(1.02);
  box-shadow: 0 4px 16px rgba(0,0,0,0.2);
}

/* Placeholder (where item will drop) */
.sortable-placeholder {
  background: rgba(59, 130, 246, 0.1);
  border: 2px dashed #3b82f6;
  border-radius: 8px;
}

/* Drag handle */
.handle {
  cursor: grab;
  padding: 8px;
  color: #9ca3af;
}

.handle:hover {
  color: #374151;
}

Real-World Examples

Kanban Board

<div class="kanban-board">
  <!-- Todo Column -->
  <div data-component="sortable"
       data-sortable-group="kanban"
       data-sortable-draggable=".card"
       data-sortable-api="/api/tasks/{id}"
       data-sortable-id-attr="data-id"
       data-sortable-stage-attr="data-status"
       data-sortable-update-field="status"
       data-status="todo">
    <h3>To Do</h3>
    <div class="card" data-id="1" draggable="true">Task 1</div>
    <div class="card" data-id="2" draggable="true">Task 2</div>
  </div>

  <!-- In Progress Column -->
  <div data-component="sortable"
       data-sortable-group="kanban"
       data-sortable-draggable=".card"
       data-sortable-api="/api/tasks/{id}"
       data-sortable-id-attr="data-id"
       data-sortable-stage-attr="data-status"
       data-sortable-update-field="status"
       data-status="in-progress">
    <h3>In Progress</h3>
    <div class="card" data-id="3" draggable="true">Task 3</div>
  </div>

  <!-- Done Column -->
  <div data-component="sortable"
       data-sortable-group="kanban"
       data-sortable-draggable=".card"
       data-sortable-api="/api/tasks/{id}"
       data-sortable-id-attr="data-id"
       data-sortable-stage-attr="data-status"
       data-sortable-update-field="status"
       data-status="done">
    <h3>Done</h3>
  </div>
</div>

Todo List

<ul data-component="sortable" data-sortable-draggable="li">
  <li draggable="true">Buy groceries</li>
  <li draggable="true">Call mom</li>
  <li draggable="true">Finish report</li>
</ul>

Table Rows

<table>
  <tbody data-component="sortable" data-sortable-draggable="tr">
    <tr draggable="true"><td>Row 1</td></tr>
    <tr draggable="true"><td>Row 2</td></tr>
    <tr draggable="true"><td>Row 3</td></tr>
  </tbody>
</table>

Keyboard Navigation

Key Action
Space Start/stop drag mode
↑/↓ Move item up/down
Enter Confirm position
Escape Cancel drag

Common Pitfalls

⚠️ 1. draggable="true" on Items

<!-- ❌ Missing draggable -->
<div class="item">Item</div>

<!-- ✅ Add draggable -->
<div class="item" draggable="true">Item</div>

⚠️ 2. Group Name Must Match

<!-- ❌ Different groups - can't move across -->
<div data-sortable-group="a">...</div>
<div data-sortable-group="b">...</div>

<!-- ✅ Same group -->
<div data-sortable-group="cards">...</div>
<div data-sortable-group="cards">...</div>