Summary
DraggableList.Root accepts drops from every other DraggableList in the page, with no public API to opt out. Consumers rendering two (or more) lists that should be independent have no way to disable cross-list dropping.
cc @ByronDWall
Where it's hardcoded
In packages/nimbus/src/components/draggable-list/components/draggable-list.root.tsx:
- A single module-level constant
DRAGGABLE_LIST_DATA_FORMAT = "nimbus-draggable-list-item" is used as both the drag payload type and acceptedDragTypes — so every Nimbus DraggableList implicitly accepts every other Nimbus DraggableList.
getDropOperation: () => "move" is hardcoded — there's no path to return "cancel" for foreign sources.
- The component constructs its own
dragAndDropHooks internally and assigns it after spreading consumer props, so a consumer-supplied dragAndDropHooks is silently overridden.
DraggableListRootProps exposes none of these knobs.
The CrossListDragAndDrop story confirms cross-list movement is the intended default — but there's no way to opt out.
Repro
<>
<DraggableList.Root aria-label="A" items={a} onUpdateItems={setA} />
<DraggableList.Root aria-label="B" items={b} onUpdateItems={setB} />
</>
Items can be dragged from A into B and vice versa, with no consumer-controlled way to prevent it.
Proposed API
Any one of the following would close the gap; my preference is the first:
dragNamespace?: string prop — when set, the list uses \nimbus-draggable-list-item:${dragNamespace}`` as its drag type and only accepts that exact type. Lists with different namespaces won't accept each other's items. Backward-compatible: unset preserves today's behavior.
acceptedDragTypes?: string[] + a way to set the outgoing drag type — direct passthrough to useDragAndDrop. More flexible, more surface area.
getDropOperation?: (...) => DropOperation override — lets consumers return "cancel" based on source/target inspection.
(1) covers the "two independent lists on one page" case with one prop and zero new concepts for consumers. (2)/(3) are useful for more advanced cases (custom drag sources, conditional acceptance) and could come later.
Workaround today
There isn't a clean one. App-level revert in onUpdateItems (detect foreign items, restore prior state) works but flashes the wrong state briefly. Forking the component is the only way to get correct UX.
Summary
DraggableList.Rootaccepts drops from every otherDraggableListin the page, with no public API to opt out. Consumers rendering two (or more) lists that should be independent have no way to disable cross-list dropping.cc @ByronDWall
Where it's hardcoded
In
packages/nimbus/src/components/draggable-list/components/draggable-list.root.tsx:DRAGGABLE_LIST_DATA_FORMAT = "nimbus-draggable-list-item"is used as both the drag payload type andacceptedDragTypes— so every Nimbus DraggableList implicitly accepts every other Nimbus DraggableList.getDropOperation: () => "move"is hardcoded — there's no path to return"cancel"for foreign sources.dragAndDropHooksinternally and assigns it after spreading consumer props, so a consumer-supplieddragAndDropHooksis silently overridden.DraggableListRootPropsexposes none of these knobs.The
CrossListDragAndDropstory confirms cross-list movement is the intended default — but there's no way to opt out.Repro
Items can be dragged from A into B and vice versa, with no consumer-controlled way to prevent it.
Proposed API
Any one of the following would close the gap; my preference is the first:
dragNamespace?: stringprop — when set, the list uses\nimbus-draggable-list-item:${dragNamespace}`` as its drag type and only accepts that exact type. Lists with different namespaces won't accept each other's items. Backward-compatible: unset preserves today's behavior.acceptedDragTypes?: string[]+ a way to set the outgoing drag type — direct passthrough touseDragAndDrop. More flexible, more surface area.getDropOperation?: (...) => DropOperationoverride — lets consumers return"cancel"based on source/target inspection.(1) covers the "two independent lists on one page" case with one prop and zero new concepts for consumers. (2)/(3) are useful for more advanced cases (custom drag sources, conditional acceptance) and could come later.
Workaround today
There isn't a clean one. App-level revert in
onUpdateItems(detect foreign items, restore prior state) works but flashes the wrong state briefly. Forking the component is the only way to get correct UX.