Skip to main content

DDInputDevices

DragDrop is device-agnostic: the same source/target registration works whether the player uses a mouse, a touchscreen, a gamepad, or the keyboard. Two backends translate raw input into the same state machine — a pointer backend (mouse + touch) and a selection backend (gamepad + keyboard). You never interact with them directly.

Gesture per device

Device Lift Move Drop Cancel
Mouse press + move past DragThresholdPx ghost follows cursor release over a target release off any target
Touch long-press (LongPressSeconds) ghost follows finger lift finger over a target lift finger off any target
Gamepad GamepadLiftKeyCode on the selected source navigate selection; ghost springs onto slots GamepadDropKeyCode GamepadCancelKeyCode
Keyboard KeyboardLiftKeyCode on the selected source navigate selection; ghost springs onto slots KeyboardLiftKeyCode again KeyboardCancelKeyCode (opt) / menu

Mouse vs. click. A press that releases without crossing the threshold is a click and fires the source's OnActivated instead of lifting. Touch vs. scroll. If a touch moves past TouchSlopPx before the long-press fires, it is treated as a scroll gesture and the lift is abandoned, so a swipe that starts on a slot still scrolls the list.

Grab mode (gamepad + keyboard) is modal rather than literal dragging: normal UI navigation keeps moving GuiService.SelectedObject, and the ghost springs onto whichever registered target the selection lands on. Drop/cancel keys are bound (and sunk) via ContextActionService only for the duration of a drag, so they never shadow those buttons elsewhere.

Switching input mid-drag reverts

If the player changes input class while a drag is live — touching the screen during a gamepad drag, picking up a controller during a mouse drag — the drag is cancelled and reverted: scroll locks are restored, the ghost is removed, and DragEnded fires with a nil target. The drag is not handed over to the new device.

Mouse and keyboard are the same class, so tapping a key during a mouse drag (or clicking during keyboard grab mode) does not cancel. The three classes are MouseKeyboard, Touch, and Gamepad.

Losing window focus or opening the Roblox menu mid-drag also reverts — those are moments where the release event might never arrive, and a stranded ghost would otherwise linger.

The Escape key

Roblox reserves Escape (it opens the menu), so DragDrop can't sink it as a dedicated cancel. In practice opening the menu already reverts the drag, so Escape effectively cancels. If you want a clean keyboard cancel that does not open the menu, set KeyboardCancelKeyCode to a key of your choosing.

Clicks right after a drop

Some button setups fire Activated immediately after a drop lands on them, which can read as an accidental click. Guard against it:

button.Activated:Connect(function()
	if DragDrop.ConsumeActivation() then
		return -- a drag just ended here; swallow this stray click
	end
	handleClick()
end)

Configuration

Every threshold and keybind is adjustable via Configure. These are behavior knobs only — nothing here styles the ghost.

DragDrop.Configure({
	DragThresholdPx = 12,
	LongPressSeconds = 0.3,
	GamepadLiftKeyCode = Enum.KeyCode.ButtonY,
	KeyboardCancelKeyCode = Enum.KeyCode.Backspace,
})

See also

Show raw api
{
    "functions": [],
    "properties": [],
    "types": [],
    "name": "DD Input Devices",
    "desc": "[DragDrop](/api/DragDrop) is device-agnostic: the same source/target\nregistration works whether the player uses a mouse, a touchscreen, a gamepad,\nor the keyboard. Two backends translate raw input into the same state machine —\na **pointer** backend (mouse + touch) and a **selection** backend (gamepad +\nkeyboard). You never interact with them directly.\n\n### Gesture per device\n\n| Device | Lift | Move | Drop | Cancel |\n| --- | --- | --- | --- | --- |\n| **Mouse** | press + move past `DragThresholdPx` | ghost follows cursor | release over a target | release off any target |\n| **Touch** | long-press (`LongPressSeconds`) | ghost follows finger | lift finger over a target | lift finger off any target |\n| **Gamepad** | `GamepadLiftKeyCode` on the selected source | navigate selection; ghost springs onto slots | `GamepadDropKeyCode` | `GamepadCancelKeyCode` |\n| **Keyboard** | `KeyboardLiftKeyCode` on the selected source | navigate selection; ghost springs onto slots | `KeyboardLiftKeyCode` again | `KeyboardCancelKeyCode` (opt) / menu |\n\n**Mouse vs. click.** A press that releases without crossing the threshold is a\n*click* and fires the source's `OnActivated` instead of lifting. **Touch vs.\nscroll.** If a touch moves past `TouchSlopPx` before the long-press fires, it is\ntreated as a scroll gesture and the lift is abandoned, so a swipe that starts on\na slot still scrolls the list.\n\n**Grab mode** (gamepad + keyboard) is modal rather than literal dragging: normal\nUI navigation keeps moving `GuiService.SelectedObject`, and the ghost springs\nonto whichever registered target the selection lands on. Drop/cancel keys are\nbound (and sunk) via `ContextActionService` only for the duration of a drag, so\nthey never shadow those buttons elsewhere.\n\n### Switching input mid-drag reverts\n\nIf the player changes input **class** while a drag is live — touching the screen\nduring a gamepad drag, picking up a controller during a mouse drag — the drag is\n**cancelled and reverted**: scroll locks are restored, the ghost is removed, and\n[DragEnded](/api/DragDrop#DragEnded) fires with a `nil` target. The drag is not\nhanded over to the new device.\n\nMouse and keyboard are the **same** class, so tapping a key during a mouse drag\n(or clicking during keyboard grab mode) does **not** cancel. The three classes\nare `MouseKeyboard`, `Touch`, and `Gamepad`.\n\nLosing window focus or opening the Roblox menu mid-drag also reverts — those are\nmoments where the release event might never arrive, and a stranded ghost would\notherwise linger.\n\n### The Escape key\n\nRoblox reserves Escape (it opens the menu), so DragDrop can't sink it as a\ndedicated cancel. In practice opening the menu already reverts the drag, so\nEscape effectively cancels. If you want a clean keyboard cancel that does **not**\nopen the menu, set `KeyboardCancelKeyCode` to a key of your choosing.\n\n### Clicks right after a drop\n\nSome button setups fire `Activated` immediately after a drop lands on them,\nwhich can read as an accidental click. Guard against it:\n\n```lua\nbutton.Activated:Connect(function()\n\tif DragDrop.ConsumeActivation() then\n\t\treturn -- a drag just ended here; swallow this stray click\n\tend\n\thandleClick()\nend)\n```\n\n### Configuration\n\nEvery threshold and keybind is adjustable via [Configure](/api/DragDrop#Configure).\nThese are behavior knobs only — nothing here styles the ghost.\n\n```lua\nDragDrop.Configure({\n\tDragThresholdPx = 12,\n\tLongPressSeconds = 0.3,\n\tGamepadLiftKeyCode = Enum.KeyCode.ButtonY,\n\tKeyboardCancelKeyCode = Enum.KeyCode.Backspace,\n})\n```\n\n---\n### See also\n\n- **[DD Getting Started](/api/DD%20Getting%20Started)** — registering sources and targets.\n- **[DD Ghosts & Scripting](/api/DD%20Ghosts%20&%20Scripting)** — the drag proxy and Scriptable mode.",
    "source": {
        "line": 88,
        "path": "lib/dragdrop/src/Docs/DD_InputDevices.luau"
    }
}