Drag drop list

An ordered list people reorder by pointer or keyboard, committing a new immutable value on each drop.

<tp-drag-drop-list>
Drag drop list
Default density Compact density Restart from the initial state Show the copyable source

Usage

Items are plain data, so the list is fed from script: set defaultValue (uncontrolled) or value (controlled) before the first render, with each item’s id and label. Each drag hovers a preview order; a drop commits a new array, and Escape or a drop outside restores the committed order. Numeric and string item IDs remain distinct, including 0, '', 1 and '1'. IDs must remain stable across reordering; an index is unsuitable as an identity.

Keyboard

Every row has a named handle Button. Focus it, press Space or Enter to pick the item up, move it with the arrow keys, and press Space or Enter again to drop it; Escape cancels. A live region announces each step. Arrow keys choose a directional sortable destination. Tab/Shift+Tab finishes and continues normal traversal without delayed focus theft.

Reading the value

tp-value-change fires with each proposed order and can be cancelled; tp-value-commit fires once the drop has settled. Read value then to save the order. A same-list change emits one change/commit pair; transfer emits one pair per changed lane. Hover, no-op and canceled/rejected drops emit no commit.

Commit events
Default density Compact density Restart from the initial state Show the copyable source
Drag a row to reprioritize.

Variants and sizes

variant is ghost (the default, rows without a boundary), outline or subdued. size steps the rows down from default through sm to xs for side panels and settings popovers.

Variants and sizes
Default density Compact density Restart from the initial state Show the copyable source

Activation and swapping

By default only the handle starts a pointer drag, which keeps text in the row selectable. activation="item" lets the whole row start one, after a short delay or movement so clicks still work. reorder-mode="swap" exchanges two items instead of shifting the ones between them. The feedback property picks the drag feedback; 'clone', used here, leaves a copy in the row’s layout position while the item moves. Mouse handles activate immediately; touch waits 250 ms with 5 px tolerance. Ordinary item activation uses a 200 ms/10 px delay or distance strictly greater than 5 px. Nested controls retain their own interaction.

Whole-row drag with swap
Default density Compact density Restart from the initial state Show the copyable source

Rich rows

renderItem returns the content of each row from its item and a context, so rows can carry Badges and their own actions; a control in the actions slot stays separately operable. The context’s preview flag marks the copy shown while dragging. renderOverlay draws the row that follows the pointer. Preview content must not create live drag registrations. Use a custom overlay for opaque shadow components.

Custom rows and overlay
Default density Compact density Restart from the initial state Show the copyable source
Drag to reorder, or open a release.

Horizontal

orientation="horizontal" lays the items out in a row and moves them with the left and right arrow keys, mirrored for right-to-left text.

Horizontal
Default density Compact density Restart from the initial state Show the copyable source

Disabled and read-only

disabled and readonly stop reordering and cancel a drag in progress, while leaving the rows readable. Use readonly when the order is visible but locked, such as while a save is pending.

Read-only
Default density Compact density Restart from the initial state Show the copyable source

Connected lists

Lists that share one DragDropManager from @tweakpad/ui/drag-drop, each with a unique group, can pass items between them, such as columns on a board. moveItem() performs the same atomic move from code, for example from a “Move to Ready” menu action. Configure each list before it first renders; here the script creates them. An empty list stays a drop target, and keyboard users can move an item into it with the arrow keys. A list without manager owns its manager. A supplied manager remains application-owned when a list disconnects. Dispose bindings and managers when their owning application is removed.

Board lanes
Default density Compact density Restart from the initial state Show the copyable source
Move first item to Ready Drag a card between lanes.

One controlled owner

With value instead of defaultValue, the lists propose and your code decides. A move between lanes proposes one tp-value-change per lane, sharing metadata.operationId; stage both, then assign both values synchronously to accept the move. Leaving them unassigned rejects it. The exact typed ID order must match each proposal; fresh item objects with those IDs are accepted. Missing acknowledgment, veto, duplicates or incompatible normalization reject every lane. Library atomicity covers committed component values and commit notifications. Application stores must stage their own connected update; unrelated external side effects in listeners cannot be rolled back by the component.

Direct Foundation callbacks and onValueChange exceptions are observed and cancel the default action. Native DOM listener exceptions follow browser dispatch semantics and cannot reliably become vetoes; use preventDefault(), the common event cancellation API or a caught suspension rejection.

Controlled lanes
Default density Compact density Restart from the initial state Show the copyable source

Authorizing a drop

The manager’s monitor reports each operation. Call event.suspend() inside dragend to hold the drop: the feedback stays where it was dropped and no value changes until you call resume() or abort() on the returned handle, for example after a permission check or a server call. settled reports the outcome. Repeated calls return one handle; the first result wins. Explicit cancellation, disconnect or manager destruction aborts the decision; no timer silently accepts it. Use try/catch around application work and abort on failure.

Approve before committing
Default density Compact density Restart from the initial state Show the copyable source
Moving a teammate between teams needs approval.

Restricting movement

modifiers take factories for the manager’s movement modifiers. RestrictToVerticalAxis keeps the dragged row on its column, which suits a long single list dragged by the whole row.

Vertical axis only
Default density Compact density Restart from the initial state Show the copyable source

API Reference

Every element also accepts motion-policy.

Attributes

AttributePropertyTypeDefaultDescriptionactivationactivation'handle' | 'item''handle'Where a pointer picks a row up: its handle, or anywhere on the row; the handle stays the keyboard target.reorder-modereorderMode'move' | 'swap''move'How a drop rearranges the list: move inserts the item, swap exchanges it with the item it lands on.variantvariant'ghost' | 'outline' | 'subdued''ghost'The row treatment, shared with List item.sizesize'xs' | 'sm' | 'default''default'The row density.labellabelstring'Items'The accessible name of the list, such as "Release checklist".

Properties

PropertyTypeDefaultDescriptiondefaultValuereadonly T[] | undefined—The items the list starts with, when it owns its value; set it before the list connects.onValueChange((event: TpValueChangeEvent<readonly T[]>) => void) | undefined—Called with each proposal, before tp-value-change is dispatched.getItemId((item: T, index: number) => UniqueIdentifier) | undefined—The stable identity of an item, which must not change as it moves; defaults to the item itself or its id.getItemLabel((item: T, index: number) => string) | undefined—The readable name of an item, used in the handle's name and in announcements; defaults to its label, then its id.renderItem((item: T, context: DragDropItemContext<T>) => unknown) | undefined—Renders the content of an item's row, with its position and drag state; without it the row shows the label.getItemOptions((item: T, context: DragDropItemContext<T>) => DragDropItemOptions) | undefined—Per-item drag settings, such as disabling dragging or dropping for one item.managerDragDropManager | undefined—The drag manager shared by lists that exchange items; a list without one owns its own.groupUniqueIdentifier—The identity of this list among lists sharing a manager; unique and stable.typeDraggableInput['type']—The kind of item this list's rows are, matched against the accept of other lists.acceptDroppableInput['accept']—Which items may be dropped into this list: a type, a list of types, or a predicate on the dragged item.sensorsManagerOptions['sensors']—The input sensors that start a drag, replacing or extending the pointer and keyboard defaults.modifiersManagerOptions['modifiers']—Transforms of the drag movement, such as locking it to an axis or snapping it to a grid.collisionDetectorDroppableInput['collisionDetector']—How the item under the drag is chosen.collisionPriorityDroppableInput['collisionPriority']—The priority of this list's targets when targets overlap.alignmentDraggableInput['alignment']—Which point of the dragged row follows the pointer.feedbackManagerOptions['feedback']—How the dragged row is shown: moved with a hidden placeholder, cloned, moved without a placeholder, or not at all.renderOverlay((item: T, context: DragDropItemContext<T>) => unknown) | undefined—Renders a custom overlay that follows the pointer while an item is dragged.rootElementManagerOptions['rootElement']—The element the drag feedback is attached to.overlayDisabledManagerOptions['overlayDisabled']—Turns the custom overlay off, for every item or for some.transitionSortTransition | null | undefined—The animation of rows making room during a pointer drag; null turns it off.keyboardTransitionManagerOptions['keyboardTransition']—The animation of rows making room during a keyboard move; null turns it off.dropAnimationManagerOptions['dropAnimation']—The animation of the dropped row settling into place; null turns it off.autoScrollManagerOptions['autoScroll']—Scrolls the nearest scroll container while a drag nears its edge.instructionsManagerOptions['instructions']—Replaces the keyboard instructions read with each handle.announcementsManagerOptions['announcements']—Replaces the announcements made as a drag starts, moves and ends; return undefined to stay silent.accessibilityManagerOptions['accessibility']—Configures, or turns off with false, the instructions and live announcements.controllerread-only—new ListController(this, this.#state)The list's binding to its drag manager, shared with connected lists.valuereadonly T[]—The items in their committed order; set it before the list connects to control it, then publish each accepted proposal back. The list never mutates the array.committedValueread-onlyreadonly T[]—The committed array; the same as value.previewValueread-onlyreadonly T[]—The order shown during a drag, before anything commits; otherwise the committed array.

Events

EventTypeDescriptiontp-value-changeTpValueChangeEvent<readonly T[]>Cancelable proposal of the new array before it commits, with the moved item, both lists and both indexes in metadata; a controlled list must publish the accepted value.tp-value-commitTpValueCommitEvent<readonly T[]>After a drop commits, once per changed list.tp-before-drag-startCustomEvent<DragEvent>Cancelable, before a drag starts; preventing it keeps the item in place.tp-drag-startCustomEvent<DragEvent>When a drag starts.tp-drag-moveCustomEvent<DragEvent>Cancelable, as the dragged item moves.tp-collisionCustomEvent<DragEvent>Cancelable, when the item under the drag is chosen.tp-drag-overCustomEvent<DragEvent>Cancelable, when the drag moves over a new place; preventing it keeps the preview order.tp-drag-endCustomEvent<DragEvent>When the item is dropped; detail.suspend() defers the decision for an asynchronous check.tp-drag-settledCustomEvent<DragEvent>When the operation finishes, with an outcome of committed, canceled or rejected.

Methods

MethodReturnsDescriptionmoveItem(id, destination)MoveItemOutcomeMoves an item to an index in this list or a connected one, through the same proposal as a drop; returns accepted, no-op or rejected.cancelDrag()voidCancels a drag involving this list and restores the committed order.

CSS parts

PartDescription::part(list)The native ordered list.::part(item)The list element around each row.::part(handle)The grip Button that picks a row up.::part(empty)The row shown while the list has no items.::part(placeholder)The stand-in that keeps the dragged row's place.::part(overlay)The custom drag overlay drawn by renderOverlay.::part(drag-drop-list-empty)The row shown while the list has no items.::part(drag-drop-list-handle)The grip Button that picks a row up.::part(drag-drop-list-item)The list element around each row; marked while dragging, dropping or targeted.::part(drag-drop-list-list)The native ordered list.::part(drag-drop-list-overlay)The custom overlay that follows the pointer.::part(drag-drop-list-placeholder)The stand-in that keeps the dragged row in place.