Skip to main content
On this pageOverview

Drag and Drop

Overview

Sortable lists and cross-container movement with pointer tracking, keyboard navigation, collision detection, auto-scrolling, and screen reader announcements.

Integration Model

DragAndDrop is different from other Foldkit UI components in two ways. First, it doesn’t have a view() function. Instead, you spread draggable() and droppable() attributes onto your own elements. Second, its update function can return Reordered and Cancelled through the optional outMessage field. You handle those OutMessages to decide how to reorder your data.

Integration requires four pieces: a DragAndDrop.Model field in your Model, an Update.foldChild fold with a foldOutMessage, DragAndDrop.subscriptions for document-level pointer and keyboard listeners, and draggable() / droppable() attributes in your view.

See it in an app

Check out how DragAndDrop is wired up in the kanban example or the UI showcase.

Examples

Demo

The snippet below shows a minimal sortable list with all four integration pieces. For a full example with persistence, cross-container moves, and add-card forms, see the Kanban example.

Backlog
Design API
Write tests
Build docs
Done
Set up repo
Add CI
Drag and drop

Styling

DragAndDrop is fully headless. You render all items, containers, and ghost elements. Use isDragging() and maybeDraggedItemId() to conditionally style items during drag (e.g. reduced opacity on the source, a drop placeholder at the target).

AttributeCondition
data-draggable-idSet on draggable items with the item ID.
data-sortable-idSet on sortable items with the item ID.
data-droppable-idSet on drop containers with the container ID.

Keyboard Interaction

DragAndDrop supports full keyboard navigation. Space/Enter activates drag mode, arrow keys move the item, Tab/Shift+Tab moves between containers, and Escape cancels.

KeyDescription
Space / EnterStarts a keyboard drag on the focused item.
Arrow Up / DownMoves the item within its container (vertical orientation).
Arrow Left / RightMoves the item within its container (horizontal orientation).
Tab / Shift+TabMoves the item to the next / previous container.
Space / EnterDrops the dragged item at its current position.
EscapeCancels the drag and returns the item to its original position.

Accessibility

Draggable items receive role="option" with aria-roledescription="draggable". Drop containers receive role="listbox". Screen reader announcements are emitted for drag start, movement, and drop via a live region.

Testing

In Story tests, use DragAndDrop.test to resolve keyboard drag Commands:

  • DragAndDrop.test.settleFocus({ itemId }) resolves the item's focus after a drop or cancellation.

  • DragAndDrop.test.resolveKeyboardMove({ itemId, targetContainerId, targetIndex }) supplies a target container and index for an arrow-key move, then resolves the item's focus.

API Reference

InitConfig

Configuration object passed to DragAndDrop.init().

NameTypeDefaultDescription
idstring—Unique ID for the drag-and-drop instance.
orientation'Vertical' | 'Horizontal''Vertical'Item flow direction. Controls arrow key mapping.
activationThresholdnumber5Minimum pointer movement in pixels before a drag activates. Prevents accidental drags from clicks.

View Helpers

Functions for attaching drag-and-drop behavior to your elements and reading drag state.

NameTypeDefaultDescription
draggable(config)ReadonlyArray<Attribute>—Spread onto draggable items. Attaches pointer-down, keyboard activation, and ARIA attributes. Config requires model, toParentMessage, itemId, containerId, and index.
droppable(containerId, label?)ReadonlyArray<Attribute>—Spread onto drop containers. Attaches the container ID for collision detection and optional ARIA label.
sortable(itemId)ReadonlyArray<Attribute>—Spread onto items that are both draggable and sortable targets.
ghostStyle(model)Option<CSSProperties>—Returns positioning styles for a ghost element that follows the pointer during drag. Use with Option.match to conditionally render.
isDragging(model)boolean—Whether a drag is currently in progress.
maybeDraggedItemId(model)Option<string>—The ID of the item being dragged, if any.
maybeDropTarget(model)Option<DropTarget>—The current drop target (containerId + index), if any.

OutMessage

Messages emitted to the parent through the optional outMessage field. Fold the OutMessage in the foldOutMessage of your Update.foldChild config.

NameTypeDefaultDescription
Reordered{ itemId, fromContainerId, fromIndex, toContainerId, toIndex }—Emitted when a drag completes with a valid drop target. The parent uses this to commit the reorder against its own data (move the item in the source array, splice it into the destination). Fold it in the foldOutMessage of your DragAndDrop fold.
Cancelled{}—Emitted when a drag is cancelled via Escape or a pointer release without a valid drop target. No reorder should be applied.