agentsclimarketplace

With drag drop

Skill robcsaszar/frontend-kit/skills/with-drag-drop

Expert guidance for implementing drag-and-drop in JavaScript from scratch — free-dragging elements, sortable lists, resizable panels, drop zones. Use when making an element draggable, adding pointer-based dragging, building a sortable or reorderable list, or implementing resize handles, without reaching for a third-party dependency. Covers a single reusable drag engine (activators, modifiers, base+offset tracking), click-vs-drag disambiguation, transform-based live tracking, pointer capture, touch support, cleanup, and keyboard accessibility. Don't use for native OS file drag-and-drop (dataTransfer, File API) or HTML5 draggable-attribute-only reordering.From its SKILL.md

Install
npx -y skills add robcsaszar/frontend-kit --skill with-drag-drop

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

10.3 KB, ~2.2k tokens by cl100k_base, as published. Nobody here has run it

Drag and Drop — Implementation Guidance

Custom pointer-driven dragging, built directly (not native HTML5 draggable/dataTransfer — that's a separate, OS-integrated mechanism this skill does not cover, and no third-party dependency is required for anything below). One core engine, extended per scenario — not several competing approaches to pick between. Apply Core Rules to everything you build; The Core Engine describes the shared shape; Extending the Engine covers each scenario.

The Core Engine

Every scenario below is the same three-part shape, just configured differently:

  1. Activators — whatever starts/moves/ends the gesture. A pointer activator (pointerdown/move/up, gated by a distance or delay threshold) is the default; a keyboard activator (Space/Enter to pick up, arrow keys to move, Escape to cancel) drives the same start/move/end lifecycle so keyboard operability is architected in, not bolted on afterward.
  2. Modifiers — an ordered pipeline of pure functions the raw delta passes through before it's applied: restrict-to-bounds, snap-to-grid, aspect-ratio lock, min/max size. Order matters — each modifier sees the previous modifier's already-adjusted values, not the raw pointer position.
  3. Position model — a base (the resting value, changed only at gesture start/end) and an offset (the live delta, changed continuously during the gesture, folded back to zero the instant it ends). See Core Rules for why these stay separate.

A free-drag element is this engine with no modifiers. A resize handle is this engine tracking width/height instead of x/y. A sortable list is this engine plus a swap/shift strategy function. A drop zone is this engine plus a collision-detection function against registered targets. Build the engine once; each scenario in Extending the Engine is a small addition to it, not a separate system.

Core Rules

These apply everywhere in the engine — a naive implementation of any one of these has broken working code in production, including this exact project's own feedback-widget FAB earlier the same day.

  • NEVER animate top/left/width/height on every frame of a live drag Instead: Track the drag delta in a transform: translate3d(x, y, 0) on top of a stable base position. A single one-shot CSS transition on left/top after the drag ends (e.g. snapping to an edge) is fine — that's one style recalculation, not one per pointermove. Why: transform is compositor-only (GPU, no layout/paint); animating box-model properties on every pointer-move event forces layout every frame and janks under load.

  • NEVER set both top+bottom (or left+right) while height/width is auto Instead: When switching a live-tracked element to an absolute resting position (e.g. after a drag snaps to an edge), explicitly cancel the unused pair (bottom: auto; right: auto;). Why: CSS resolves this as over-constrained by solving for the missing dimension — the box stretches to fill the gap between the two anchors instead of sizing to content. A leftover bottom class combined with a newly-set top will silently stretch the element to fill the viewport.

  • NEVER represent "resting position" and "live drag delta" with the same property Instead: Keep them as two separate values — a base position (left/top, updated only at drag start/end) and a transform delta (updated continuously during the drag, reset to zero when folded into the base). Change only one of the two in any given reactive update. Why: Updating both in the same tick to represent one animated transition (e.g. "jump the base and compensate with an equal-and-opposite transform offset, then animate the offset back to zero") is fragile — if the two values ever commit to the DOM in separate paints, the element visibly flashes to the un-compensated position for a frame.

  • NEVER use event.offsetX/event.offsetY as the drag reference point Instead: Compute deltas from event.clientX/clientY relative to the pointer's own position at drag start, or relative to a fixed container's getBoundingClientRect(). Why: offsetX/offsetY are relative to whichever element is currently under the cursor — as the pointer crosses child elements mid-drag, the reference frame silently changes underneath you, corrupting the computed delta.

  • NEVER treat any pointer movement as the start of a drag Instead: Require a distance threshold (typically 1–10px of movement, or a short delay for long-press activation) before calling it a drag. Below threshold, let the native click fire normally. Why: Without a threshold, the sub-pixel movement inherent in any real click misfires as a drag, and legitimate click handlers on the same element never fire.

  • NEVER rely only on mouseleave or a same-element mouseup to end a drag Instead: Use the Pointer Events API and call element.setPointerCapture(event.pointerId) on drag start; handle pointercancel identically to pointerup. Also end the drag on the window's blur event (alt-tab, a native file picker or dialog stealing focus mid-drag) — capture doesn't help here since the pointer never actually released. Why: Without capture, releasing the pointer outside the original element never fires that element's mouseup/pointerup — the drag gets stuck "on" until the cursor re-enters, then resumes unexpectedly. mouseleave-as-cancel is a symptom fix; pointer capture removes the underlying problem, but neither one fires on a focus-stealing interrupt — that needs its own blur handler.

  • NEVER wire mouse events only Instead: Use Pointer Events (pointerdown/pointermove/pointerup/pointercancel) — one API unifies mouse, touch, and pen. If Pointer Events aren't available, wire mouse and touch event pairs explicitly, and filter the synthetic mouse events browsers fire ~300–500ms after a touch (check event.timeStamp deltas) so touch drags don't double-fire. Set touch-action: none on the draggable element so the browser's own scroll/pinch/long-press gestures don't fight the drag. Why: Mouse-only handlers are silently broken on every touchscreen — no error, the element just doesn't drag.

  • NEVER leave listeners or timers attached past the component's lifetime Instead: Track every addEventListener (especially window/document-level ones added during drag start) and remove them on drag end and on unmount; clear any setTimeout/requestAnimationFrame handles the same way. Why: Leaked document-level listeners keep firing after the component is gone, causing phantom state updates or drags on unrelated later interactions.

  • NEVER ship a pointer-only activator for content reordering Instead: Give the engine a second activator — keyboard (Space/Enter to pick up, arrow keys to move, Space/Enter to drop, Escape to cancel) — sharing the exact same start/move/end lifecycle as the pointer activator, plus an aria-live region announcing state changes. Why: Pointer-only dragging is unusable for keyboard and screen-reader users. This is the gap that's easiest to skip because the pointer path alone looks "done."

Extending the Engine

MANDATORY READ before implementing any of these — references/patterns.md has the full worked engine plus every extension below.

NeedAdd to the core engine
Free-floating element (FAB, floating panel, canvas object)Nothing extra — the base engine alone, with an optional edge/grid snap on release. references/patterns.md#engine
Resize handle / panelTrack width/height instead of x/y; add a min-size modifier. Resize is legitimately layout-affecting — accept the reflow cost, there's no compositor-only substitute for "this box's size follows my pointer." references/patterns.md#resize
Sortable/reorderable listAdd a swap/shift strategy function (computes each non-dragged item's displacement from cached rects) and a plain index array as the source of truth — don't hand-roll splice/insert math, off-by-one errors on boundary items are the most common bug. If the list scrolls, dragging near an edge needs to auto-scroll the container (track scroll direction/intent, step scrollTop). references/patterns.md#sortable
Drop zoneAdd a collision-detection function against registered targets. Pick the strategy by target shape and density: closest-center for simple, sparse, similarly-sized targets; rect-intersection when targets vary in size and real overlap area should decide the winner; pointer-within when targets are small/precise. Fire enter/leave/drop against exactly one "winning" zone when zones overlap. references/patterns.md#dropzone
Snap-to-grid / restrict-to-bounds / aspect-ratio lockAdd a modifier function to the pipeline — keep the order deliberate, each modifier sees the previous one's adjusted values. references/patterns.md#modifiers
Physical "throw" on releaseAdd an inertia calculation: above a minimum release speed, glide on an exponentially-decaying velocity to an analytically-solved stop point; below it, do a short eased "smooth end." Only add this when a tactile/physical feel is wanted (a canvas or board), not by default. references/patterns.md#inertia
Element must render above a scrollable/overflow:hidden ancestorDrag a fixed-position clone instead of moving the source node in place, then animate the clone to the final position on drop and swap back. references/patterns.md#overlay
Smooth performance in long listsIsolate per-frame position state to the dragged item's own subscriber — don't broadcast it through state that re-renders every item in the list on every pointer move.

What ships with it: 1 file

15.7 KB alongside SKILL.md

references/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.