webdevpuneet.com

SortableJS Draggable Task Board — Trello-Style Kanban Snippet

SortableJS Draggable Task Board · Dashboards · Plain HTML, CSS & JS · Live preview

CategoryDashboards

Copy & paste

Source Code

Requires
<div class="stb-stage">
  <div class="stb-head">
    <span class="stb-tag">SortableJS · shared group</span>
    <h2>Task Board</h2>
    <p>Drag any card within a column to reorder it, or drop it into another column — every list shares one SortableJS group.</p>
  </div>
  <div class="stb-board">
    <div class="stb-col">
      <div class="stb-col-head"><span class="stb-dot stb-dot-todo"></span>To Do<span class="stb-count" id="countTodo">0</span></div>
      <div class="stb-list" id="colTodo" data-col="todo"></div>
    </div>
    <div class="stb-col">
      <div class="stb-col-head"><span class="stb-dot stb-dot-progress"></span>In Progress<span class="stb-count" id="countProgress">0</span></div>
      <div class="stb-list" id="colProgress" data-col="progress"></div>
    </div>
    <div class="stb-col">
      <div class="stb-col-head"><span class="stb-dot stb-dot-done"></span>Done<span class="stb-count" id="countDone">0</span></div>
      <div class="stb-list" id="colDone" data-col="done"></div>
    </div>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Cross-column dragging via group
One shared group string is the entire mechanism enabling drops between separate lists.
Independent per-list instances
Each column is its own Sortable() call, not a single instance spanning the board.
Animated reflow
animation: 150 smoothly slides neighboring cards as a drag lands among them.
Three distinct drag-state classes
ghostClass, chosenClass, and dragClass style separate moments of a drag independently.
Live count badges
Column counts re-read the DOM on every sort rather than tracking a separate variable.
No re-render on drop
SortableJS moves the real DOM node, so a dropped card keeps its content and listeners intact.
Touch and mouse support
SortableJS handles both pointer types without separate code paths.
Responsive 3-to-1 column layout
The board collapses to a single stacked column on narrow viewports.

About this UI Snippet

SortableJS Draggable Task Board — What group Actually Unlocks

Screenshot of the SortableJS Draggable Task Board snippet rendered live

A single new Sortable(list, {...}) call makes one list's items reorderable within themselves — that part is the library's default behavior with zero extra configuration. This task board needs more than that: a card started in "To Do" has to be droppable into "In Progress" or "Done." That specific capability, drag *between* separate DOM containers, comes from exactly one option: group.

Why a shared group string, not a shared instance

js document.querySelectorAll('.stb-list').forEach(function (list) { new Sortable(list, { group: 'tasks', ... }); });

Each column gets its own Sortable instance — there's no single object managing all three lists together. What ties them into one drag surface is that every instance is configured with the identical string 'tasks' for group. SortableJS checks this value at drag time: when a card is picked up from one list, it looks at every *other* Sortable-managed list on the page and asks "does your group match mine?" Only lists with a matching group become valid drop targets; a fourth list configured with group: 'archive' would visually sit right next to these columns but refuse every drop from them. This is what makes group a genuinely different mechanism from just calling Sortable on a parent wrapping all three columns — SortableJS is explicitly designed around independent instances that opt into cross-container dragging by name, not a single instance spanning multiple lists.

animation: 150 and the reflow

animation: 150 is what makes the *other* cards in a list slide smoothly out of the way as a dragged card passes over or lands among them, rather than snapping instantly to their new positions. It's measured in milliseconds and applies to every reflow SortableJS triggers — both within a list during reorder and across lists when a card arrives from elsewhere.

The class hooks: ghost, chosen, drag

Three separate classes cover three separate moments of a drag, and mixing them up is a common source of "why does my board look wrong while dragging" bugs: - ghostClass styles the placeholder left behind in the original position while dragging — this snippet dims it to 35% opacity so it reads as "the space this card used to occupy." - chosenClass styles the card the instant it's picked up, for the whole duration of the drag, including after it's dropped in its animation-in — this snippet gives it a glowing outline. - dragClass styles the actual element following the cursor/touch point during the drag itself.

onSort and keeping counts honest

SortableJS fires onSort on the list the drop landed in whenever its child order changes — from a drag *or* a drop arriving from elsewhere. This snippet uses it to re-run updateCounts(), which just re-reads each column's live .children.length rather than maintaining a separate count variable that could drift out of sync with the actual DOM. Because SortableJS moves the real DOM node on drop (it doesn't clone and destroy), the moved card carries all of its original content and listeners with it automatically — there's no re-render step needed after a drop.

Step by step

How to Use

  1. 1
    Add the SortableJS CDN scriptOne script tag — no companion CSS is shipped by the library.
  2. 2
    Give every column list the same group valuegroup: "tasks" on all three Sortable instances is what enables cross-column drops.
  3. 3
    Create one Sortable instance per listEach column gets its own new Sortable() call, not one shared instance for the whole board.
  4. 4
    Set animation for a smooth reflowanimation: 150 makes other cards slide out of the way instead of snapping instantly.
  5. 5
    Style the three drag-state classesghostClass, chosenClass, and dragClass each cover a different moment of the drag.
  6. 6
    Use onSort to keep derived state in syncRe-read live DOM counts rather than maintaining a separate counter that can drift.

Real-world uses

Common Use Cases

Trello-style task boards
Build a three-column board in which one shared group string lets cards drag within and between columns, with 150 ms animated reflow.
Pipeline and ticket tracking
Move leads, tickets or applicants through stages, with each column as its own independent Sortable() instance.
Styled drag states
Style the card being held, the placeholder left behind and the clone under the pointer separately using ghostClass, chosenClass and dragClass.
Group option teaching
Show that a single option is the whole mechanism for cross-list dragging, with the default single-list behaviour needing no configuration at all.
Admin dashboard widget lists
Offer a reorderable, categorised card list for an internal tool, where neighbouring cards slide smoothly out of the way as a drag lands.

Got questions?

Frequently Asked Questions

It tells SortableJS which lists are allowed to exchange dragged items with each other. Every list configured with the same group value becomes a valid drop target for cards dragged from any other list sharing that value; lists with a different or missing group refuse those drops.

SortableJS is designed around one instance per draggable container, with group being the mechanism that connects otherwise-independent instances for cross-container drags. There is no single-instance API for "one Sortable spanning three columns" — group is how the library solves that instead.

ghostClass styles the placeholder left in the original position during a drag. chosenClass styles the card itself for the whole time it is selected, including drop animation. dragClass styles specifically the element actively following the cursor or touch point. They cover three different visual moments and can be styled independently.

No. SortableJS moves the actual DOM node from one list to another on drop rather than destroying and recreating it, so anything attached to that element — event listeners, data attributes, content — survives the move automatically.

onSort fires whenever a list's children change due to a drag, whether that's a reorder or an arrival from another column. Re-reading list.children.length directly from the DOM inside that callback guarantees the displayed count always matches reality, instead of relying on a separate counter that could get out of sync if an update path is missed.

It sets, in milliseconds, how long SortableJS takes to animate other items sliding into their new positions as a drag moves through or lands in a list. Without it, neighboring cards would jump to new positions instantly with no transition.