The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
Make a source draggable<li draggable="true" data-item-id="task-42"> Review draft </li>View examples
Write a typed payloadevent.dataTransfer.setData('application/x-task-id', taskId)View examples
Limit source operationsevent.dataTransfer.effectAllowed = 'move'View examples
Set drag feedbackevent.dataTransfer.setDragImage(preview, 16, 16)View examples
Accept drag entryzone.addEventListener('dragenter', event => event.preventDefault())View examples
Enable droppingzone.addEventListener('dragover', event => event.preventDefault())View examples
Show copy feedbackevent.dataTransfer.dropEffect = 'copy'View examples
Inspect offered formatsevent.dataTransfer.types.includes('text/plain')View examples
Inspect transfer items[...event.dataTransfer.items].some(item => item.kind === 'file')View examples
Read dropped textconst id = event.dataTransfer.getData('application/x-task-id')View examples
Complete a custom dropevent.preventDefault()View examples
Clean up after draggingsource.addEventListener('dragend', () => source.classList.remove('dragging'))View examples
Offer a button alternative<button type="button" data-action="move"> Move to Done </button>View examples

HTML drag and drop is an event-driven enhancement for moving or copying data with a pointing device. A source writes a small, typed payload during dragstart; a target advertises acceptance during dragenter and dragover, then consumes the payload during drop. Keep the transferred value narrowly scoped, render dropped text as text rather than markup, and always offer an ordinary control for keyboard, touch, and assistive-technology users.

Step by step

Detailed examples

01

Write drag data only when the drag starts

Set draggable=true on intentional custom sources and populate dataTransfer in dragstart, the only drag event whose data store is normally writable. Transfer an opaque identifier rather than serialized HTML or sensitive records, and include text/plain only when a meaningful text fallback is appropriate. effectAllowed constrains copy, move, or link semantics; setDragImage changes visual feedback but not the operation itself.

Start an application-scoped move
<li id="task-42" draggable="true" data-item-id="task-42">Review draft</li>
<script>
  const source = document.querySelector('#task-42');
  source.addEventListener('dragstart', event => {
    event.dataTransfer.setData('application/x-task-id', source.dataset.itemId);
    event.dataTransfer.setData('text/plain', source.textContent.trim());
    event.dataTransfer.effectAllowed = 'move';
    source.classList.add('dragging');
  });
</script>

Note: Do not put authentication tokens, private object contents, or trusted HTML in a drag payload. Treat data received from another document as untrusted input.

Supply optional pointer feedback
source.addEventListener('dragstart', event => {
  const preview = document.querySelector('#drag-preview');
  event.dataTransfer.setDragImage(preview, 16, 16);
});
Back to quick reference ↑
02

Explicitly accept a compatible drag

A custom target must cancel dragover or the browser normally rejects the drop. Cancel dragenter as well when the offered types are acceptable, and set dropEffect during dragover to communicate the intended operation. The requested dropEffect must be compatible with the source's effectAllowed; otherwise the resulting operation is none.

Accept only an internal task payload
const zone = document.querySelector('#done-list');
const taskType = 'application/x-task-id';

zone.addEventListener('dragenter', event => {
  if (event.dataTransfer.types.includes(taskType)) event.preventDefault();
});

zone.addEventListener('dragover', event => {
  if (!event.dataTransfer.types.includes(taskType)) return;
  event.preventDefault();
  event.dataTransfer.dropEffect = 'move';
});

Note: Do not cancel every page-level dragover indiscriminately; doing so can turn unrelated elements into apparent drop targets and interfere with browser behavior.

Back to quick reference ↑
03

Inspect formats and items during protected events

DataTransfer.types lists offered string formats and includes Files when files are present. DataTransfer.items exposes each item's kind (string or file) and type. During events such as dragenter and dragover the store is protected: code can enumerate formats and kinds to decide whether to show acceptance, but should not expect getData or files to reveal the underlying payload until drop.

Distinguish files, URLs, and plain text
zone.addEventListener('dragenter', event => {
  const types = event.dataTransfer.types;
  const carriesFiles = types.includes('Files');
  const carriesURL = types.includes('text/uri-list');
  const carriesText = types.includes('text/plain');

  zone.dataset.offer = carriesFiles ? 'files' : carriesURL ? 'url' : carriesText ? 'text' : 'unsupported';
});

zone.addEventListener('dragleave', () => {
  delete zone.dataset.offer;
});
Inspect item metadata without consuming it
const offered = [...event.dataTransfer.items].map(item => ({
  kind: item.kind,
  type: item.type || '(unknown)'
}));
Back to quick reference ↑
04

Consume and validate data in the drop handler

The drag data store becomes readable for drop. Cancel the drop event, request the exact type you accepted, validate the value against current application state, and perform the smallest DOM or model update needed. Never insert dropped text with innerHTML, and validate dropped URLs by parsing them and allowing only schemes and destinations your feature actually supports.

Move a known task without HTML injection
zone.addEventListener('drop', event => {
  event.preventDefault();
  const id = event.dataTransfer.getData('application/x-task-id');
  if (!/^task-[a-z0-9-]+$/.test(id)) return;

  const task = document.getElementById(id);
  if (!task || !task.matches('[data-item-id]')) return;
  zone.append(task);
  delete zone.dataset.offer;
});
Render dropped plain text safely
zone.addEventListener('drop', event => {
  event.preventDefault();
  const text = event.dataTransfer.getData('text/plain').slice(0, 500);
  const output = document.querySelector('#dropped-text');
  output.textContent = text;
});

Note: A MIME-looking transfer type describes a format; it does not prove that the contents are safe or authentic.

Back to quick reference ↑
05

Pair visual drag feedback with ordinary controls

Use dragstart, dragenter, dragleave, drop, and dragend to expose clear transient state, then remove it on every completion path. Nested children can make dragenter and dragleave fire more often than expected, so style from current accepted state rather than assuming one enter/leave pair. Pointer dragging alone is not a complete interaction: provide named buttons, menus, or other keyboard-operable controls that invoke the same model action, announce important results through a status region, and test touch behavior separately.

Clean up state and announce the result
source.addEventListener('dragend', () => {
  source.classList.remove('dragging');
  document.querySelectorAll('[data-offer]').forEach(element => {
    delete element.dataset.offer;
  });
});

function moveTask(task, destination) {
  destination.append(task);
  document.querySelector('#move-status').textContent = `${task.textContent.trim()} moved.`;
}
Expose an equivalent button
<li id="task-7">
  <span>Publish notes</span>
  <button type="button" data-action="move">Move to Done</button>
</li>
<p id="move-status" role="status"></p>
Back to quick reference ↑

Local code tester

Try a movable task

Drag the task to Done, or use the equivalent button. The demo transfers only a validated local identifier and makes no network requests.

Runs in your browser
Preview

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. WHATWGHTML Standard: Drag and drophtml.spec.whatwg.org
  2. WHATWGHTML Standard: The DataTransfer interfacehtml.spec.whatwg.org
  3. MDN Web DocsDataTransferdeveloper.mozilla.org
  4. MDN Web DocsHTML Drag and Drop APIdeveloper.mozilla.org

Help us improve

Found a typo or missing example?

Tell us what would make this cheat sheet clearer, more complete, or more useful.

Share feedback