Roblox GUI Scripting Basics: ScreenGui, Buttons, and Click Events
Last updated:
Quick answer: Roblox GUI scripting works by placing a ScreenGui inside the player's PlayerGui, adding Frames, TextLabels, and TextButtons to it, connecting a click event such as Activated in a LocalScript, and forwarding anything that changes gameplay to the server with a RemoteEvent.
Roblox GUI scripting means building the on-screen interface — menus, buttons, counters — out of GUI objects and wiring their events with LocalScripts. A button that actually works needs three things in place: the GUI objects must live somewhere a LocalScript can find them (the player's PlayerGui), a LocalScript must connect a click event like Activated or MouseButton1Click, and anything that changes the game itself must be forwarded to the server through a RemoteEvent.
What objects make up a Roblox GUI?
Every interface is a tree of GUI objects. A ScreenGui is the root container that makes its children appear on the screen; inside it you lay out Frame panels, TextLabel text, TextButton buttons, and TextBox inputs. If you skip the ScreenGui and parent a TextButton directly to PlayerGui, nothing renders.
The usual hierarchy looks like this:
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local screenGui = Instance.new("ScreenGui")
screenGui.Name = "ShopGui"
screenGui.ResetOnSpawn = false
screenGui.Parent = player:WaitForChild("PlayerGui")
local frame = Instance.new("Frame")
frame.Name = "ShopFrame"
frame.Size = UDim2.fromScale(0.4, 0.5)
frame.Position = UDim2.fromScale(0.3, 0.25)
frame.BackgroundColor3 = Color3.fromRGB(30, 30, 40)
frame.Parent = screenGui
local title = Instance.new("TextLabel")
title.Name = "Title"
title.Size = UDim2.fromScale(1, 0.2)
title.BackgroundTransparency = 1
title.Text = "Shop"
title.TextColor3 = Color3.new(1, 1, 1)
title.Parent = frame
local buyButton = Instance.new("TextButton")
buyButton.Name = "BuyButton"
buyButton.Size = UDim2.fromScale(0.6, 0.25)
buyButton.Position = UDim2.fromScale(0.2, 0.6)
buyButton.Text = "Buy sword"
buyButton.Parent = frame
Two details matter here. UDim2.fromScale sizes everything relative to the parent, so the layout survives different screen sizes. And naming objects (Name = "BuyButton") is not cosmetic — those names are how you find the objects again from other scripts.
In practice you will build most GUIs by hand in Studio and only script their behavior, but understanding the tree keeps you from the classic beginner trap of parenting a button to the wrong container.
How do I find GUI objects safely?
GUI objects appear in the player's PlayerGui at runtime, and the order in which scripts and GUIs load is not guaranteed. Indexing with player.PlayerGui.ShopGui on the first frame can error with attempt to index nil. Use WaitForChild to wait until each level of the tree exists:
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
local screenGui = playerGui:WaitForChild("ShopGui")
local frame = screenGui:WaitForChild("ShopFrame")
local buyButton = frame:WaitForChild("BuyButton")
Where you put the LocalScript changes what it can reach:
- Inside the ScreenGui (for example in StarterGui) — simplest; the script can walk up from
script.Parentand works well for one menu. - StarterPlayerScripts — runs once per player; good for a UI controller that manages several GUIs.
- StarterCharacterScripts — runs every respawn; use it only for HUDs that should reset with the character.
Also decide deliberately whether the GUI should survive respawns. ResetOnSpawn = true (the default) destroys and rebuilds the ScreenGui every time the character dies, which resets menus to their starting state. Set ResetOnSpawn = false for shops, settings, and anything that should persist. If a script holds a reference to an object from the old copy of the GUI, it points at a destroyed instance after the next respawn — one of the most common "my button stopped working after I died" bugs.
Which click event should I use: MouseButton1Click or Activated?
For a TextButton, use Activated. It fires for mouse clicks, touch taps, and gamepad button presses, so one handler covers every device:
buyButton.Activated:Connect(function()
print("Buy button pressed")
end)
The alternatives each have a narrower job:
MouseButton1Click— fires only for left mouse button clicks. Fine on desktop, dead on mobile, which is most of Roblox's audience.MouseButton2Click— right-click, useful for context menus.InputBegan— raw input on any input type; powerful but you must filter input types yourself.
Always use :Connect (capital C). Activated is also nicer than MouseButton1Click because it respects the button's Active state and only fires when the press starts and ends on the button, so dragging off the button cancels the action — exactly what you want for purchases.
One gotcha that makes it look like the event "does nothing": if another GUI element sits on top of the button (higher ZIndex over the same screen area), the click hits the top element instead. A full-screen transparent Frame is the usual culprit.
How do I open and close a menu with a tween?
Menus feel far better when they slide instead of blinking in. Keep one boolean for the menu state, and tween the Frame's Position or Size with TweenService:
local TweenService = game:GetService("TweenService")
local OPEN_POSITION = UDim2.fromScale(0.3, 0.25)
local CLOSED_POSITION = UDim2.fromScale(0.3, 1.2) -- just below the screen
local tweenInfo = TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
local menuOpen = false
local function setMenuOpen(open)
menuOpen = open
local target = open and OPEN_POSITION or CLOSED_POSITION
TweenService:Create(frame, tweenInfo, { Position = target }):Play()
end
toggleButton.Activated:Connect(function()
setMenuOpen(not menuOpen)
end)
Keeping the state in a variable (menuOpen) instead of reading frame.Visible lets you animate without ever hiding the frame, and gives you a clean flag to block other input while the menu is open. The TweenService guide covers easing styles, tweening multiple properties at once, and cleaning up tweens properly.
How does a button change gameplay on the server?
A LocalScript can only change the local player's own client. Firing a purchase, giving coins, or spawning anything must happen in a server Script, and the bridge between them is a RemoteEvent. Create the RemoteEvent once on the server (a Script in ServerScriptService), then have the button fire it:
-- LocalScript inside the GUI
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local buyItem = ReplicatedStorage:WaitForChild("BuyItem")
buyButton.Activated:Connect(function()
buyItem:FireServer("Sword")
end)
-- Script in ServerScriptService
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local buyItem = Instance.new("RemoteEvent")
buyItem.Name = "BuyItem"
buyItem.Parent = ReplicatedStorage
buyItem.OnServerEvent:Connect(function(player, itemName)
if typeof(itemName) ~= "string" then
return -- never trust what the client sent
end
-- validate against real prices, then grant the item server-side
print(player.Name .. " wants to buy " .. itemName)
end)
The first argument of OnServerEvent is always the player who fired it, so never accept a player argument from the client. Treat every other argument as untrusted input: an exploiter can fire your RemoteEvent with any values, at any time. The full request/response pattern is covered in the RemoteEvents guide.
Common mistakes that break GUIs
- Button works before dying, then never again. The ScreenGui has
ResetOnSpawnenabled (the default), so every respawn destroys the GUI your LocalScript is holding references to. SetResetOnSpawn = falseor re-resolve references after respawn. - Nothing appears on screen. Usually the object was never parented to a ScreenGui in PlayerGui, the script set
Parentbefore configuring properties and something errored midway, orVisibleis false. Check the Explorer at runtime, not just in edit mode. - Clicks land on the wrong element. ZIndex decides stacking order: a sibling with a higher ZIndex (or a later parent order at the same ZIndex) eats the click. Raise the button's ZIndex or shrink the overlapping element.
- GUI is transparent and clicks fall through.
BackgroundTransparency = 1still catches clicks whenActive = true(buttons default to active), which is how invisible full-screen overlays block your whole interface. - The connect line never runs. A LocalScript placed in Workspace or ServerScriptService simply does not run. LocalScripts execute in the player's character, PlayerGui, or PlayerScripts. If you are not sure why nothing fires at all, see the WaitForChild infinite yield fix — a stuck WaitForChild silently stops the script before it ever reaches your event connection.
Learn Luau by doing
GUI scripting only clicks once you have wired a real button to real behavior. On Luablox you can practice the language itself through /theory, drill event and table patterns on /problems, and follow the structured Luablox lessons that build from script fundamentals up to full gameplay systems.
Frequently asked questions
Why does my TextButton do nothing when I click it?
Work through four checks: is the button inside a ScreenGui that is parented to PlayerGui, is a LocalScript actually running and reaching the :Connect line (no earlier error, no stuck WaitForChild), is another element with a higher ZIndex covering the button, and did a respawn destroy the GUI because ResetOnSpawn is still true? Also make sure you connected Activated or MouseButton1Click on the button itself.
Where does the LocalScript for my GUI go?
Put it inside the ScreenGui (in StarterGui) for single-menu scripts, in StarterPlayerScripts for a controller that manages several GUIs, or in StarterCharacterScripts for HUDs that should reset on respawn. LocalScripts do not run in Workspace or ServerScriptService — if yours is there, it never executes.
What is the difference between MouseButton1Click and Activated?
MouseButton1Click only fires for left mouse clicks, so it never triggers on phones or tablets. Activated fires for mouse, touch, and gamepad input and only when the press begins and ends on the button, which makes it the right default for almost every button.
How do I open and close a menu with a button?
Keep a menuOpen boolean in a LocalScript, and on each button press flip it and tween the Frame to an open or closed Position (or Size) with TweenService. Animating position instead of toggling Visible gives you smooth motion and keeps a single state variable as the source of truth.
Can a LocalScript give a player coins when they click a button?
No — client-side changes do not affect the game. The button fires a RemoteEvent to the server, and a server Script validates the request and updates the real values (for example the ones in leaderstats). Never trust amounts or item names sent from the client.
Learn core Roblox scripting and Luau programming concepts, including: roblox gui scripting tutorial, TextButton click, MouseButton1Click, ScreenGui tutorial, roblox button does nothing.