Skip to content
CodeLifter

Draggable element

Drag with a mouse, finger or pen, or move it with the arrow keys. The element stays inside its container.

CL-0057 fixesChecked 2026-10-15
An orange-red luggage tag at the end of a long string trailing across the paper

1. The prompt

Not yet run against a named assistant · 110 words · CC-BY-4.0

Write a draggable element in plain JavaScript using pointer events, with listeners on the element itself, not on document. On pointerdown with the main button, call setPointerCapture and remember the offset between the pointer and the element, so a fast drag keeps hold and the element does not jump. Move it with transform, not top and left, and clamp it inside its parent. Make it focusable with tabindex="0", give it an accessible name that mentions the arrow keys, and move it 10 pixels per arrow key press, or 50 with Shift, calling preventDefault only for the arrows. Set touch-action: none on the element only, and add an MIT licence comment.

draggable.js JavaScript · 26 lines Reviewed
// MIT licence. CodeLifter editors, codelifter.com/prompts/draggable-element/
const area = document.getElementById("area");
const card = document.getElementById("card");
let x = 0, y = 0, dx = 0, dy = 0;
function place(nx, ny) { // clamp to the container's inner box
x = Math.min(Math.max(nx, 0), area.clientWidth - card.offsetWidth);
y = Math.min(Math.max(ny, 0), area.clientHeight - card.offsetHeight);
card.style.transform = `translate(${x}px, ${y}px)`;
}
card.addEventListener("pointerdown", (e) => {
if (e.button !== 0) return; // main button or touch contact only
card.setPointerCapture(e.pointerId);
dx = e.clientX - x;
dy = e.clientY - y;
});
card.addEventListener("pointermove", (e) => {
if (card.hasPointerCapture(e.pointerId)) place(e.clientX - dx, e.clientY - dy);
});
card.addEventListener("keydown", (e) => {
const s = e.shiftKey ? 50 : 10;
const moves = { ArrowLeft: [-s, 0], ArrowRight: [s, 0], ArrowUp: [0, -s], ArrowDown: [0, s] };
if (!moves[e.key]) return;
e.preventDefault(); // stop the page scrolling
place(x + moves[e.key][0], y + moves[e.key][1]);
});
2. What the prompt should produce. Load it after the markup below.

What a one-line prompt gets you

Representative output for “make a draggable div in JavaScript” and nothing else, written by the editors to show the pattern. It runs. It also leaves these gaps.

AI-generatedunreviewed
Shown for comparison. Do not ship.
var el = document.getElementById("drag");
el.onmousedown = function(e) {
var offX = e.clientX - el.offsetLeft;
var offY = e.clientY - el.offsetTop;
document.onmousemove = function(e) {
el.style.left = (e.clientX - offX) + "px";
el.style.top = (e.clientY - offY) + "px";
};
document.onmouseup = function() {
document.onmousemove = null;
};
};
Representative one-line-prompt output, written by the editors to show the pattern. Not a recorded generation.
  1. 1 It listens for mouse events only.The reviewed code uses pointer events with pointer capture, so one set of handlers serves mouse, pen and touch, and each drag follows only the pointer that started it.Prompt line: “Write a draggable element in plain JavaScript using pointer events.”
  2. 2 A finger drag scrolls the page.MDN's touch-action page says the browser handles panning by default and cancels the pointer when it takes over. The sample sets no touch-action; the reviewed code sets none on the element only.Prompt line: “Set touch-action: none on the element only.”
  3. 3 It can be dragged out of sight.Nothing limits the position, so the element can leave its container or the screen. The reviewed place() function clamps every move.Prompt line: “Clamp it inside its parent.”
  4. 4 Keyboard users cannot move it.It takes no focus and ignores keys. The reviewed element has tabindex="0", a name that mentions the arrow keys, and arrow-key movement.Prompt line: “Make it focusable with tabindex="0", give it an accessible name that mentions the arrow keys, and move it 10 pixels per arrow key press.”
  5. 5 It overwrites document handlers.Assigning document.onmousemove and document.onmouseup replaces any handler another script set there. The reviewed listeners sit on the element.Prompt line: “With listeners on the element itself, not on document.”
  6. 6 It moves the element with left and top.The sample reads offsetLeft and writes style.left, two records of one position. The reviewed code keeps a single x and y pair and applies it with transform, so clamping deals with one pair of numbers.Prompt line: “Move it with transform, not top and left.”
  7. 7 It carries no licence.Nothing records who wrote it or on what terms. The reviewed script starts with an MIT licence line and a credit.Prompt line: “Add an MIT licence comment.”

Install it

  1. 1Paste the container and the card where the draggable area should appear. The container needs position: relative and a size.
  2. 2Put your own content in the card and update its aria-label to name what the reader is moving.
  3. 3If the card holds links or buttons, make a small handle inside it the drag target instead, so clicks on the controls still work.
  4. 4Load draggable.js after the markup.
  5. 5Test with a mouse, with touch in your browser's device emulation, and with the keyboard alone: Tab to the card, then use the arrows and Shift with the arrows.
index.html HTML · 6 lines Reviewed
<div id="area" style="position:relative;height:300px;border:1px solid">
<div id="card" tabindex="0" role="group" aria-label="Movable note. Arrow keys move it."
style="position:absolute;left:0;top:0;width:140px;padding:8px;background:#eee;
touch-action:none;cursor:grab;user-select:none">Drag me, or use the arrow keys</div>
</div>
<script src="draggable.js"></script>

Questions and answers

Has this prompt been tested with an assistant?

Not yet. The editors wrote the prompt to describe the reviewed code, which they wrote and tested with AI assistance and re-ran in a scripted DOM test on 2026-10-15. Nobody has run the prompt against a named assistant, so compare any answer with the code above.

Why does the drag need setPointerCapture?

MDN describes setPointerCapture() as making an element the capture target of future pointer events, so later events for that pointer go to it until capture is released or pointerup fires. A fast drag therefore keeps hold even when the pointer slides off the card.

Why does dragging on a phone scroll the page instead?

MDN says panning and pinching are handled by the browser by default, and a page using pointer events receives pointercancel when the browser starts handling a touch gesture. Set touch-action: none on the card, never on the whole page, because MDN warns it may stop zooming.

How does it stay inside the container?

place() clamps each new x and y between zero and the container's clientWidth or clientHeight minus the card's offsetWidth or offsetHeight. If the container can change size, call place(x, y) again after a resize.

Does this meet WCAG 2.5.7 Dragging Movements?

Not on its own. The W3C Understanding document asks that anything done by dragging can also be done with a single pointer without dragging, and says keyboard support alone does not meet it. Add Move buttons or x and y number inputs that can be clicked or tapped.

Do the arrow keys interfere with Tab or scrolling?

No. The handler calls preventDefault() only for the four arrow keys, so Tab and every other key behave normally, while the arrows move the card instead of scrolling the page.

Can I save the position?

Yes. Store x and y in localStorage inside place() and call place() with the saved values when the page loads.

Can I use the script in a commercial project?

Yes. It is MIT licensed: keep the licence line or credit CodeLifter in your notices.

Sources

  1. 1Element: setPointerCapture() methodMDN Web Docs, checked 2026-10-15
  2. 2touch-actionMDN Web Docs, checked 2026-10-15
  3. 3Understanding SC 2.5.7: Dragging MovementsW3C WAI, WCAG 2.2, checked 2026-10-15
  4. 4PointerEventMDN Web Docs, checked 2026-10-15

More prompts

All six prompts