Drag & Drop
A standalone, dependency-free drag & drop library built on the native HTML5 drag-and-drop API. Shipped as its own bundle lp-dragdrop-*.js / lp-dragdrop-*.css, loadable without launchpad.js.
How to load (standalone)
Drop these two tags into any HTML page — no launchpad.js required.
1. Basic sortable
Drag any item to reorder. Uses the native HTML5 drag-and-drop API.
- Drag me anywhere
- I can be reordered
- No handle required
- Works on touch too
2. Drag handle
Restrict drag initiation to a specific child element.
- ☰ Only the handle starts a drag
- ☰ Clicks on the text are ignored
- ☰ Good for rows with buttons
3. Horizontal list
Direction is auto-detected from flex-direction.
- Apple
- Banana
- Cherry
- Date
- Elderberry
4. Grid sortable
Works with CSS grid and flex-wrap layouts.
- 🎨
- 🚀
- 🎯
- ⚡
- 🔥
- 💎
- 🌟
- 🎉
5. Live reorder vs drop-only
Two modes: liveReorder: true moves items in the DOM as you drag; liveReorder: false (default) only commits on drop.
Live reorder
- Live Item 1
- Live Item 2
- Live Item 3
Drop only (default)
- Drop Item 1
- Drop Item 2
- Drop Item 3
6. Cross-container (shared group)
Drag between containers with the same group name.
List A
- Task 1
- Task 2
- Task 3
List B
- Task 4
- Task 5
7. Kanban (asymmetric pull/put rules)
Three columns, all sharing group kanban.
Todo
- Design mockup
- Write spec
- Gather feedback
Doing
- Build API
- Create UI
Done
- Project kickoff
8. Filter — pinned items are not draggable
Items matching filter selector cannot be dragged.
- 📌 Pinned — cannot drag
- Draggable
- Draggable
- 📌 Pinned — cannot drag
- Draggable
9. Disabled state
Toggle drag on/off via enable() / disable().
- Item 1
- Item 2
- Item 3
10. Storage persistence (localStorage)
Reorder, then refresh the page — order is remembered.
- Persisted item 1
- Persisted item 2
- Persisted item 3
- Persisted item 4
11. Callbacks (onStart, onEnd, onReorder)
Wire up callbacks via the constructor options.
- Callback 1
- Callback 2
- Callback 3
12. Custom DOM events
Listen via element.addEventListener("lp:dragdrop:start", ...).
Events bubble, so you can also listen on any ancestor.
- Event item 1
- Event item 2
- Event item 3
12. API methods
- One
- Two
- Three
- Four
13. Different element types
Works with any HTML element, not just <li>. Here are <div>s and a sortable table body.
Divs
Table rows
| # | Name | Role |
|---|---|---|
| 1 | Alice | Admin |
| 2 | Bob | Editor |
| 3 | Carol | Viewer |
Configuration reference
| Option | Type | Default | Purpose |
|---|---|---|---|
items | selector | null | Which descendants are draggable (falls back to direct children) |
handle | selector | null | Restrict drag to a handle element inside each item |
filter | selector\|fn | null | Items matching are NOT draggable |
group | string\|object | null | Group name for cross-container drag ({name, pull, put}) |
liveReorder | boolean | false | Move DOM during drag instead of only on drop |
accept | selector\|fn | null | Validate drop targets |
disabled | boolean | false | Initially disabled |
storage | "local"\|"session" | null | Persistence backend |
storageKey | string | auto from el.id | Custom storage key |
dataIdAttr | string | "data-id" | Attribute used for order serialization |
dragRotation | boolean | true | Tilt the drag ghost 2° for visual flair |
eventPrefix | string | "lp" | Prefix for dispatched DOM CustomEvents (e.g. lp:dragdrop:start) |
onStart, onMove, onReorder, onEnd, onAdd, onRemove, onFilter | function | noop | Lifecycle callbacks |
Data attributes (for auto-init)
Any option can be set declaratively via data-lp-*. kebab-case converts to camelCase.
| Attribute | Maps to |
|---|---|
data-lp-component="dragdrop" | Enables auto-init on the element |
data-lp-handle=".handle" | handle: ".handle" |
data-lp-filter=".pinned" | filter: ".pinned" |
data-lp-group="shared" | group: "shared" |
data-lp-live-reorder="true" | liveReorder: true |
data-lp-storage="local" | storage: "local" |
data-lp-storage-key="my-key" | storageKey: "my-key" |
data-lp-disabled="true" | disabled: true |
Events reference
All events are CustomEvents dispatched on the container element. They bubble — you can listen on any ancestor. Every event carries a detail object with the relevant items.
| Event | Fires when | event.detail |
|---|---|---|
lp:dragdrop:start |
A drag begins (dragstart fired, ghost captured) | { item, from } |
lp:dragdrop:move |
Cursor moves over an item during drag (every dragover) | { item, from, to } |
lp:dragdrop:reorder |
An item has been dropped to a new position | { item, from, to } |
lp:dragdrop:add |
An item was added to this container from another (cross-group drop) | { item, from, to } |
lp:dragdrop:remove |
An item was removed from this container into another | { item, from, to } |
lp:dragdrop:end |
Drag ends, whether dropped or cancelled | { item, from } |
lp:dragdrop:filter |
A drag was blocked because the item matched the filter | { item } |
Change the prefix via the eventPrefix option (defaults to "lp"). Setting it to "app" produces app:dragdrop:start, etc.
CSS classes applied by the plugin
| Class | Applied to |
|---|---|
.lp-dragdrop-drag | Source item while it is being dragged (dimmed) |
.lp-dragdrop-over | Item currently being dragged over |
.lp-dragdrop-over-before | Drop indicator: drop will land before this item |
.lp-dragdrop-over-after | Drop indicator: drop will land after this item |
.lp-dragdrop-over-empty | Empty container accepting drop at end |
.lp-dragdrop-reject | Target refuses the drop (accept validation failed) |
.lp-dragdrop-disabled | Whole container with drag disabled |