SfSkills Domains Docs GitHub

Home / lwc / drag-and-drop

drag-and-drop

Implement drag-and-drop in LWC using HTML5 Drag and Drop API, keyboard alternatives, accessible announcements, and file drop zones. NOT for handling a file the user dropped onto the component - use lwc/file-upload-patterns.

Open the skill folder on GitHub


LWC Drag and Drop

Native HTML5 Drag and Drop works in LWC — no library needed. Three things make it harder than it looks, and all three fail silently: dragover must call preventDefault() or the drop is rejected; dataTransfer is only valid during the event; and LWC has no event modifiers, so the ondragover.prevent syntax people bring from Vue is ignored without a warning.

Build Order: Keyboard First, Drag Last

Drag is a pointer gesture with no fallback. HTML5 drag events do not fire on touch and are unreachable by keyboard, so a drag-only component is unusable by keyboard users, screen-reader users, and every mobile user at once.

  1. Explicit controls — Alt+Up / Alt+Down on a focusable row (plus Home/End

    and Escape). Works everywhere.

  2. Announcements — an aria-live region carrying pick-up, each move with

    position and total, and cancellation. aria-grabbed and aria-dropeffect are deprecated in ARIA 1.1; do not use them.

  3. Drag — layered on a component that already works.

Built in that order the component is correct on every device from the first commit. Built drag-first, the keyboard path is retrofitted onto a state machine designed around pointer events, and it shows.

Adoption Signals

Any reordering UI (priority list, board columns, file drop zones). Not for complex trees or cross-window drag — wrap a specialised library in one component for those, and verify it under Lightning Web Security in a real org.

  1. Template: draggable="true" plus ondragstart, ondragover, ondragenter,

    ondragleave, ondragend, ondrop, and onkeydown. Use lwc:ref / this.refs for DOM access rather than this.template.querySelector().

  2. event.preventDefault() inside the dragover handler — there is no .prevent

    modifier in LWC, and without the call drop never fires.

  3. dataTransfer.setData synchronously in dragstart, getData synchronously

    in drop. Outside those windows reads return an empty string.

  4. Reorder by id array, copying before mutating and reassigning the property.

    splice on a reactive array in place does not re-render.

  5. Cleanup in dragend, not drop — dragend fires whether or not a drop

    occurred, so abandoned drags do not leave stuck state. Guard dragleave against descendant elements with an enter/leave counter.

  6. Jest: assert preventDefault was called on dragover, cover both interaction

    paths, and assert the announcement contains the position and the total. jsdom has drag events but no DataTransfer — stub it.

Key Considerations

Worked Examples (see references/examples.md)

Common Gotchas (see references/gotchas.md)

Top LLM Anti-Patterns (full list in references/llm-anti-patterns.md)

Official Sources Used

Package files