Known limitations
Scope
- Vertical lists only. Lists scroll and reorder vertically. Horizontal lists, grids with 2D reordering and freeform canvases are not supported. A board of side-by-side vertical columns works.
- Items move between lists, never outside them. There is no free dragging to an arbitrary position, and no dropping files from the desktop.
- No enter predicate. A droppable has no input that rejects some items. Bind its
disabledinput from the drag state instead; see Accepting only some items.
Hit-testing
Because hit-testing uses geometry rather than elementFromPoint(), some browser behavior is not reproduced:
- Overlays do not block drops. A modal or toolbar painted over a droppable does not stop the droppable underneath from being hit.
- Stacking order is approximated. "Last in document order wins" matches the paint order in most layouts. It does not follow
z-indexor stacking contexts, so a droppable earlier in the DOM but raised visually loses to a later sibling.
If your layout depends on either, keep overlapping droppables in different groups, or avoid painting interactive overlays across active drop targets during a drag.
Drag preview
- The default preview duplicates IDs. The clone keeps the
idattributes of the item and everything in it, so styles that select the item through an ID keep applying to the preview. During a drag the item is hidden (display: none), not removed, so the document has two elements with each of those IDs.getElementById()andquerySelector('#…')find whichever comes first in the document, usually the item. A<label for>, anaria-labelledby, or an SVG reference such asurl(#gradient)or<use href="#icon">in the preview can resolve to the hidden item, and browsers may not paint an SVG gradient or symbol that is inside a hidden element. The preview isinertandaria-hidden="true", so nothing in it takes focus or reaches screen readers. If duplicate IDs matter in your app, use a custom preview template.
Shadow DOM
Lists, items and the drag preview work inside an open shadow root, for example in a component with ViewEncapsulation.ShadowDom. Droppables register themselves rather than being found by a document query, and the lookups that walk up from an element (the list of an item, the vdndScrollable container of a list) continue from a shadow root to its host. Some things still need care:
- An item must be in the same DOM tree as its list. A list finds its items with
querySelectorAll(), which does not look into shadow roots. For a row component that uses shadow DOM, putvdndDraggableon the component's host element, not on an element inside its shadow root. Items slotted (<slot>) into a list that a web component renders in its shadow root are not found either. - Only open shadow roots are supported. Nothing can look into a closed one from outside:
PositionCalculatorService.findDraggableAtPoint(), for example, stops at its host. - The preview leaves the shadow root. It moves to a container under
<body>, where the shadow root's styles (its<style>elements andadoptedStyleSheets) don't apply. The default preview still looks like the item, because the clone gets the item's computed styles inline. A custom preview template declared in a shadow-DOM component loses that component's styles, so style it from a global stylesheet. An item that is itself a shadow host previews without its shadow content (cloning an element does not copy its shadow root); give it a custom preview template. - No grabbing cursor inside shadow roots. The
cursor: grabbingthe library sets during a drag comes from a document stylesheet, which does not reach into shadow roots. Lists and items there keep their own cursor.
Animations on rows that move
This is a limitation of Angular. The lists move a row's view with ViewContainerRef.move(), as *ngFor does, and Angular animates a view that moves as if it left and came back: it plays the row's leave animation (animate.leave) and its enter animation (animate.enter). Only @for avoids that. Rows move when the items change order, for example after a drop or a sort. A row in stickyItemIds also moves each time it scrolls past the edge of the rendered rows.
What happens to a row with animate.leave then depends on the Angular version:
- Before Angular 21.2.1, Angular removes the row from the page when its leave animation ends, so it disappears.
- From Angular 21.2.1, the row stays, unless it moves again before its leave animation ends: then Angular removes it.
- On Angular 21, the row also keeps its leave class after the animation. A leave style that holds its end state, such as a transition to
opacity: 0, leaves the row invisible.
To avoid it during a drag, turn the animations off: give [animate.leave] an empty value from dragStart until the drop has rendered. Angular reads the value when the animation runs, after the render that moves the rows, so turn it back on in the read phase of that render:
Bind [animate.enter] to a signal and clear it the same way, so moved rows don't replay their enter animation either.
Before Angular 21.2.1 this is not enough, because Angular removes a row that moves even when its leave animation is empty. Update Angular, or keep animate.leave off rows that can move.
This only covers drags. Turn the animations off the same way around a sort of your own, and keep animate.leave off rows in stickyItemIds: they move while the list scrolls. A bound [animate.leave] also keeps a row from being recycled, even while its value is empty (see Row recycling).