How to make a ui dragging system without UIDragDetector

Can someone tell me how to make a ui dragging system without UIDragDetector

I’m worried that UIDragDetector will deprecated be in the future

I also wanna learn to make something a customizable system

1 Like

Depends on what you would like to do with it, but one way is to detect if the player is clicking the UI, if the player is then update the ui’s position accordingly.

No, when the user is dragging a frame, this is for a plug-in

i’ve made something simple a while back, i’ll hide the code just in case you want to make it yourself, anyways i recommend using Tags to detect which ui can be dragged, what you mostly need is the mouse position in the ui when the user pressed on the ui, which you can use in UserInputService.InputChanged to move the ui, make sure to use AbsolutePosition, and to consider the ui object’s AnchorPoint & AbsoluteSize, as well as the Inset the screengui it belongs to has

Script Here
local Dragging = false
local DragLocation = Vector2.zero
local DraggingUI = nil

local GUI = game.Players.LocalPlayer.PlayerGui

-- /

repeat task.wait() until #GUI:GetDescendants() >= #game.StarterGui:GetDescendants()
for _, SGUI in GUI:GetChildren() do -- the reason why i did this instead of just using CollectionService is so i can easily get the IgnoreGuiInset from the ScreenGUI it belongs to
	if not SGUI:IsA('ScreenGui') then continue end
	for _, Descendant in pairs(GUI:GetDescendants()) do
		if Descendant:IsA('GuiObject') and Descendant:HasTag('Draggable') then
			if not Descendant:FindFirstChild('DRAG-HITBOX') then
				local hitbox = Instance.new('ImageButton')
				hitbox.Name = 'DRAG-HITBOX'
				hitbox.BackgroundTransparency = 1
				hitbox.ImageTransparency = 1
				hitbox.Size = Descendant:GetAttribute('DRAG-HITBOX-SIZE') or UDim2.new(1, 0, 1, 0)
				hitbox.ZIndex = 0
				hitbox.Parent = Descendant

				hitbox.InputBegan:Connect(function(input, gpe)
					if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch and not gpe then
						Dragging = true
						DraggingUI = Descendant
						DragLocation = Vector2.new(input.Position.X, input.Position.Y) - Descendant.AbsolutePosition - (SGUI.IgnoreGuiInset and game:GetService("GuiService"):GetGuiInset() or Vector2.zero)
					end
				end)
			end
		end
	end
end

game.UserInputService.InputChanged:Connect(function(input, gpe)
	if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch and not gpe then
		if Dragging and DraggingUI then
			local position = Vector2.new(input.Position.X, input.Position.Y) - DragLocation + (DraggingUI.AbsoluteSize * DraggingUI.AnchorPoint)
			DraggingUI.Position = UDim2.new(0, position.X, 0, position.Y)
		end
	end
end)

game.UserInputService.TouchMoved:Connect(function(input)
	if Dragging and DraggingUI then
		local position = Vector2.new(input.Position.X, input.Position.Y) - DragLocation + (DraggingUI.AbsoluteSize * DraggingUI.AnchorPoint)
		DraggingUI.Position = UDim2.new(0, position.X, 0, position.Y)
	end
end)

game.UserInputService.TouchEnded:Connect(function(input)
	Dragging = false
	DraggingUI = nil
end)

game.UserInputService.InputEnded:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
		Dragging = false
		DraggingUI = nil
	end
end)```
4 Likes

I’m sorry, to ask you this

But can you give me explanation of InputChanged

What is the difference between AbsoluteSize and AbosolutePosition, then the regular size and position?

InputChanged → https://create.roblox.com/docs/reference/engine/classes/UserInputService#InputChanged
it is used mainly to detect of the player moved their mouse / finger and gives us an input object which we can extract the positions from

Size & Position give us the position & size relative TO the parent of the ui object, so if i put a frame of the position 0, 0 in another tiny frame that i moved somewhere in the bottom, that frame is still going to have the position 0, 0 but in reality it’s at the bottom of the screen too, so AbsolutePosition tells us exactly where it is, same applies to AbsoluteSize

For a plugin (or any custom drag), you don’t need UIDragDetector. The usual loop is: capture offset on press → move on InputChanged → clear on release.

Absolute vs Scale/Offset

  • Position / Size — relative to the parent (UDim2 scale+offset).
  • AbsolutePosition / AbsoluteSize — screen-space pixels right now (after layouts, scales, insets).

Dragging wants screen pixels, then you write back a UDim2 the parent understands.

InputChanged

Fires while an input is active and its values change (mouse move / finger move). That’s your drag tick. Pair it with InputBegan / InputEnded (or InputBegan on a hitbox + UserInputService.InputEnded).

Minimal plugin-friendly drag

local UserInputService = game:GetService("UserInputService")
local GuiService = game:GetService("GuiService")

local function makeDraggable(guiObject: GuiObject, screenGui: ScreenGui)
	local dragging = false
	local grabOffset = Vector2.zero -- cursor relative to object top-left

	local function inset(): Vector2
		return if screenGui.IgnoreGuiInset then Vector2.zero else GuiService:GetGuiInset()
	end

	guiObject.InputBegan:Connect(function(input)
		if input.UserInputType ~= Enum.UserInputType.MouseButton1
			and input.UserInputType ~= Enum.UserInputType.Touch then
			return
		end
		dragging = true
		local cursor = Vector2.new(input.Position.X, input.Position.Y)
		grabOffset = cursor - guiObject.AbsolutePosition - inset()
	end)

	UserInputService.InputChanged:Connect(function(input)
		if not dragging then return end
		if input.UserInputType ~= Enum.UserInputType.MouseMovement
			and input.UserInputType ~= Enum.UserInputType.Touch then
			return
		end

		local cursor = Vector2.new(input.Position.X, input.Position.Y) - inset()
		local topLeft = cursor - grabOffset

		-- honor AnchorPoint so the object doesn’t jump
		local size = guiObject.AbsoluteSize
		local ap = guiObject.AnchorPoint
		local parent = guiObject.Parent
		if not parent or not parent:IsA("GuiObject") and not parent:IsA("LayerCollector") then
			return
		end

		-- parent-relative pixel position of the anchor point
		local parentAbs = if parent:IsA("GuiObject") then parent.AbsolutePosition else Vector2.zero
		local parentInset = if parent:IsA("ScreenGui") then inset() else Vector2.zero
		local desiredAnchorScreen = topLeft + size * ap
		local rel = desiredAnchorScreen - (parentAbs + parentInset)

		guiObject.Position = UDim2.fromOffset(rel.X, rel.Y)
	end)

	UserInputService.InputEnded:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1
			or input.UserInputType == Enum.UserInputType.Touch then
			dragging = false
		end
	end)
end

Customization knobs worth keeping

  • Drag handle child (title bar) so content clicks don’t start a drag
  • Clamp to parent AbsoluteSize
  • Optional grid snap (math.floor(x / step) * step)
  • For plugins, prefer DockWidgetPluginGui / widget mouse APIs if you’re inside a widget rather than PlayerGui

UIDragDetector is fine for many experiences; a hand-rolled drag like this is still the right learning path when you want full control (plugins, custom snap, multi-select).

1 Like

userinputservice and math
the script depends on how your ui is set up because you need to account for different element sizes

input changed is when any physical input device moves, such as mouse

absolutesize and absoluteposition give you the pixel offset size and position instead of the relative scale value

local selector = {}

local guiObjectPosition
local mousePosition
local isDragging = false 

function selector.drag(guiObject: GuiObject, screenGui: ScreenGui)

	local UserInputService = game:GetService('UserInputService')
	
	UserInputService.InputBegan:Connect(function(input, isTyping)
		
		if isTyping then return end
		
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			
			isDragging = true
			
			mousePosition = Vector2.new(input.Position.X, input.Position.Y)
			guiObjectPosition = Vector2.new(guiObject.AbsolutePosition.X, guiObject.AbsolutePosition.Y)
		end
	end)
	
	UserInputService.InputEnded:Connect(function(input, isTyping)
		
		if isTyping then return end
		
		isDragging = false
	end)
	
	UserInputService.InputChanged:Connect(function(input, isTyping)
		
		if isTyping then return end
		
		if isDragging == true then
			local NewPosition = guiObjectPosition + ((Vector2.new(input.Position.X, input.Position.Y) - mousePosition))
			guiObject.Position = UDim2.fromOffset(NewPosition.X,NewPosition.Y)	
		end
	end)
end

return selector

See the problem I have with this script, when i hold left click and move the mouse, it drags the UI even when I’m not touching the ui

he used llm for that Reply btw

Can you please elaborate on that I’m confused what do you mean by llm?

large language model (Ai Slop)

1 Like

You can use UserInputService and calculate the mouse/touch offset when the player starts dragging, then update the UI position based on the input’s movement. Hope this helps!

I mean… they came out 2 years ago, the odds that they randomly deprecate it anytime in the near future is very slim

But it’s still better to learn how to do things by yourself

all ui elements have a second event for when the mouse enters or leaves it
personally if I were to use this I would add a second layer of checking to see if another variable controlled by this is true before setting dragging to true
alternatively you could use absolute position and size combined with mouse position
this is probably the best method since you can get a boolean directly out of it

he means chatgpt or gemini or claude or something

I knew what they meant by large language model, but thanks for the help

1 Like