Roblox GUI Maker logoRoblox GUI Maker
Unofficial third-party tool · Not affiliated with Roblox Corporation

How to Make a Draggable GUI in Roblox — Complete Roblox Guide

Last updated: 2026-06-20 · 253 words · 2 min read

A draggable GUI lets the player reposition a panel — chat window, minimap, settings menu. Roblox deprecated Frame.Draggable in 2023, so you write the behavior in ~30 lines of Luau. This guide gives a copy-paste pattern that works on mouse, touch, and gamepad, plus screen-clamping logic.

Why Frame.Draggable was deprecated

Roblox removed Frame.Draggable because it only worked with mouse input — touch and gamepad were silently broken. Manually implementing drag with UserInputService handles every input type and lets you clamp the panel to the screen.

The draggable pattern

Listen to InputBegan on the frame to start dragging. Capture the start position. Listen to InputChanged on UserInputService for every movement. Listen to InputEnded to stop. Update the frame's Position by adding the input delta to the captured start position.

local UserInputService = game:GetService("UserInputService")
local frame = script.Parent

local dragging = false
local dragStart, startPos

frame.InputBegan:Connect(function(input)
  if input.UserInputType == Enum.UserInputType.MouseButton1
    or input.UserInputType == Enum.UserInputType.Touch then
    dragging = true
    dragStart = input.Position
    startPos = frame.Position
    input.Changed:Connect(function()
      if input.UserInputState == Enum.UserInputState.End then
        dragging = false
      end
    end)
  end
end)

UserInputService.InputChanged:Connect(function(input)
  if not dragging then return end
  if input.UserInputType == Enum.UserInputType.MouseMovement
    or input.UserInputType == Enum.UserInputType.Touch then
    local delta = input.Position - dragStart
    frame.Position = UDim2.new(
      startPos.X.Scale,
      startPos.X.Offset + delta.X,
      startPos.Y.Scale,
      startPos.Y.Offset + delta.Y
    )
  end
end)

Clamp to screen bounds

Without clamping, players drag the panel off the edge and lose it. Read AbsoluteSize on the screen and frame, calculate max X/Y, and clamp the new Position to that range.

local screenSize = workspace.CurrentCamera.ViewportSize
local frameSize = frame.AbsoluteSize

local newX = math.clamp(startPos.X.Offset + delta.X, 0, screenSize.X - frameSize.X)
local newY = math.clamp(startPos.Y.Offset + delta.Y, 0, screenSize.Y - frameSize.Y)

frame.Position = UDim2.new(0, newX, 0, newY)

Adding a drag handle

Move the InputBegan listener from the frame to a child Frame named DragHandle. The math is identical; only the input source changes.

Persisting the position with DataStore

Players expect layout choices to survive a server hop. Write final UDim2 components to a DataStore keyed by UserId, and restore on player join.

Touch and gamepad gotchas

Touch input fires InputBegan for both Touch and MouseButton1. Guard with a dragging flag to avoid two simultaneous drags. Gamepad does not have a pointer; support select + d-pad to nudge instead.

Step-by-step summary

  1. 1

    Add the input listeners

    Connect InputBegan, InputChanged, and InputEnded to track drag state.

  2. 2

    Apply the delta to Position

    Add the input delta to startPos offset components.

  3. 3

    Clamp to screen bounds

    Use math.clamp so the frame stays inside ViewportSize − AbsoluteSize.

  4. 4

    Move listeners to a drag handle

    Restrict drag to a title bar if needed.

  5. 5

    Persist to DataStore

    Save final position keyed by UserId on InputEnded.

FAQ

Why does the panel jump on the first drag?

You read frame.Position after dragging started. Capture startPos inside InputBegan.

Does this work on mobile?

Yes — UserInputType.Touch is handled like MouseButton1.

Can I limit dragging to one axis?

Yes — keep startPos.X.Offset or Y.Offset constant.