To implement native HTML drag and drop, mark the source with draggable="true", put a stable identifier in event.dataTransfer during dragstart, cancel dragover on destinations that accept drops, and read and validate the identifier in drop. Your code—not the browser—must then update the application state. For reordering, also provide a way to achieve the same result without dragging.
Build a basic native drag-and-drop interaction
This example moves a task from a source list into a destination list. It transfers an ID, not an element or JavaScript object, and updates an array as the source of truth before rendering the result.
<ul id="source-list">
<li draggable="true" data-id="task-42">Prepare release notes</li>
</ul>
<ul id="destination-list" aria-label="Destination tasks"></ul>
<script>
const sourceList = document.querySelector('#source-list');
const destinationList = document.querySelector('#destination-list');
const transferType = 'application/x-example-task-id';
// In a real application, populate and update this state through your app's
// normal state-management and rendering code.
const tasks = new Map([
['task-42', { id: 'task-42', title: 'Prepare release notes', list: 'source' }]
]);
sourceList.addEventListener('dragstart', (event) => {
const item = event.target.closest('[draggable="true"][data-id]');
if (!item || !sourceList.contains(item)) return;
event.dataTransfer.setData(transferType, item.dataset.id);
event.dataTransfer.effectAllowed = 'move';
});
destinationList.addEventListener('dragover', (event) => {
// Cancel the default so this list accepts a drop.
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
});
destinationList.addEventListener('drop', (event) => {
event.preventDefault();
const id = event.dataTransfer.getData(transferType);
const task = tasks.get(id);
if (!task || task.list !== 'source') return;
// Apply your app's destination rules here before changing state.
task.list = 'destination';
render();
});
function render() {
sourceList.replaceChildren();
destinationList.replaceChildren();
for (const task of tasks.values()) {
const item = document.createElement('li');
item.textContent = task.title;
item.dataset.id = task.id;
item.draggable = true;
(task.list === 'source' ? sourceList : destinationList).append(item);
}
}
</script>
The script uses event delegation on the source list, so it continues to handle items created by render(). In a production app, reconcile the example map with your actual state store and rendering framework rather than maintaining a separate copy.
Understand the drag event lifecycle
A typical native drag proceeds through dragstart on the source, dragenter and dragleave as it crosses elements, repeated dragover events at the current target, drop at an accepted destination, and dragend at the source. The browser suppresses ordinary device input events during a native drag, so mouse or keyboard handlers do not replace this lifecycle. See the MDN drag operations guide and the WHATWG HTML Living Standard.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstall#1 Best Overall
dragstart: choose the payload and allowed operation. It is also a suitable place to mark the source visually.dragenteranddragleave: optionally show feedback when a pointer enters or leaves a target.dragover: callpreventDefault()on a valid target so it can receive a drop; setdropEffectto communicate the intended operation.drop: prevent the default when appropriate, inspect the expected payload type, validate it and the destination, then update application state.dragend: clear temporary styling or drag state whether the operation succeeded or was canceled.
Most elements do not accept drops by default. Although the specification says dragenter should also be canceled, MDN notes that browsers do not implement that requirement in practice; canceling dragover is the practical step that makes a target accept a drop. Consult the MDN event guidance.
Transfer data safely and update state deliberately
DataTransfer carries text or files; it does not pass arbitrary JavaScript object references between pages or applications. Use a type-tagged string, stable ID, or serialized value, then validate it in the drop handler. The MDN HTML Drag and Drop API reference describes the data-transfer model.
Rank #2
- Use a stable record ID rather than a display label, array index, or DOM node. Labels can change, and indexes can become stale when items are reordered.
- Check that the ID resolves to an item in current application state. Treat missing, malformed, or unexpected values as invalid.
- Check that the destination is allowed for that item and operation. A canceled drag or invalid drop must not partially change state.
- Render from the updated state. Moving a DOM node alone can leave the interface inconsistent with the data model or be undone by a later render.
- Use a custom MIME-style type such as
application/x-example-task-idto distinguish your payload from unrelated text or files.
For a sortable list, the destination may need to identify a position as well as a container. Compute the insertion point from the target item and pointer position, validate it, and update the ordered data structure; the native API does not choose or persist your application’s ordering rules.
Make the action accessible without dragging
Do not make dragging the only way to operate a feature. WCAG 2.2 Success Criterion 2.5.7 requires that functionality using a dragging movement can also be achieved by a single pointer without dragging, unless dragging is essential or the functionality is determined by the user agent and not modified by the author. The criterion is in the WCAG 2.2 specification; its rationale and examples are explained in WAI’s Understanding Dragging Movements.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesKeyboard access and a single-pointer alternative are separate needs: adding keyboard controls alone does not meet the single-pointer requirement. For reordering, offer controls such as Move up and Move down, or let a user select an item and then choose its destination. WAI’s G219 technique describes these kinds of alternatives.
- Make the alternative action discoverable and label its result clearly.
- After a move, keep focus in a useful place, such as on the moved item or its relevant control.
- Expose the changed order or destination to assistive technology users; do not rely on color or position alone to communicate success.
- Test the drag interaction and its non-drag alternative independently, including keyboard operation where the feature supports it.
Choose native drag and drop or a custom interaction
Native HTML drag and drop is a direct fit for browser-native file drops and can also support in-page moves. For sortable lists and boards, decide based on the intended devices and the control you need over behavior—not on an assumption that one approach is universally best.
Rank #4
| Decision | What to consider |
|---|---|
| Dropping files into a page | Use the native file-drop flow when users are bringing files into the browser page, and implement the file handling your interface needs. |
| Mouse and touch behavior | Check that the chosen interaction works for the devices and input methods your users need. If you implement pointer-driven behavior, account for touch as well as mouse rather than assuming native drag events cover every desired interaction. |
| Keyboard and single-pointer access | Provide keyboard operation where needed and a separate single-pointer route that does not require dragging, as required by WCAG 2.2 SC 2.5.7 unless an exception applies. |
| State and drop placement | For a board or sortable list, decide how the target position is identified, validated, and committed to application state. |
| Feedback and control | Choose the implementation that gives you the feedback, cancellation, and drop-position control your interaction requires. |
Troubleshoot common failures
- The target never receives a useful drop: confirm that the intended target handles
dragoverand callspreventDefault()for allowed drops. - The payload is empty: confirm the source is draggable,
setData()runs indragstart, and the type passed togetData()exactly matches the type passed tosetData(). - The wrong item moves: transfer a stable ID and look it up in current application state instead of relying on text or an old array index.
- A drop moves an item into an invalid location: validate the item, operation, and destination before changing state.
- The UI changes but resets on the next render: update the application’s source-of-truth data and render from it instead of only moving DOM nodes.
- Temporary drag styling remains after cancellation: remove it in
dragend, which fires whether the drag succeeded or was canceled.
Or skip the browser setup
If your goal is to capture a page screenshot rather than implement an in-page drag interaction, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request can return a PNG, JPEG, WebP, or PDF. For example, save a WebP screenshot with cURL:
Quick Recap
Best Value
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for request options. Cookie banners are accepted and removed before capture, along with supported newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. Sign up for ScreenshotNeo and get 1,000 free screenshots a month, no card required.
Free tools Windows power users keep installed
One-click scans. No signup required.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




