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:

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:

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

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.

Related pages