Roblox Menu Script Tutorial: Complete Main Menu System (2026)
2026-07-21 · Roblox GUI Maker Team
Menu structure: one shell, many pages
A good menu uses one shell Frame with a tab bar and a content area, and swaps the content panels when the player clicks a tab. Do not build separate full-screen menus for each page - that means duplicated transitions and more bugs. Instead, create one ScreenGui, put a tab bar on the left or top, and place each page (Play, Settings, Store) as a Frame inside the content area. Show one page and hide the rest; clicking a tab swaps which is visible with a Tween transition.
Page switching with Tween transitions
When the player clicks a tab, hide the current page and show the new one with a smooth transition. A common pattern is sliding the old page out and the new page in over 0.3 seconds. Use TweenService on each page's Position, and use a table to track which page is currently visible so the switch is clean. Keep transitions under 0.3 seconds - menus that take too long to open feel slow, especially on mobile.
1-- Main Menu - Client Script2-- Place in: StarterGui (LocalScript)34local Players = game:GetService("Players")5local TweenService = game:GetService("TweenService")67local player = Players.LocalPlayer8local gui = script.Parent9local pages = {10 Play = gui:WaitForChild("PlayPage"),11 Settings = gui:WaitForChild("SettingsPage"),12 Store = gui:WaitForChild("StorePage"),13}14local currentPage = "Play"1516local function switchPage(name)17 if name == currentPage then return end18 local old = pages[currentPage]19 local new = pages[name]20 -- Slide old page out, new page in21 TweenService:Create(old, TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.In),22 { Position = UDim2.new(-1, 0, 0, 0) }):Play()23 task.delay(0.25, function()24 old.Visible = false25 new.Position = UDim2.new(1, 0, 0, 0)26 new.Visible = true27 TweenService:Create(new, TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),28 { Position = UDim2.new(0, 0, 0, 0) }):Play()29 end)30 currentPage = name31end3233-- Wire tab buttons34for name, _ in pairs(pages) do35 local tab = gui:WaitForChild("Tab_" .. name)36 tab.MouseButton1Click:Connect(function()37 switchPage(name)38 end)39end
Settings panel: sliders and toggles
The settings page gives players control over volume, graphics, and music. Use sliders (a draggable handle on a track) for volume and toggles (on/off switches) for music and fullscreen. Bind the volume slider to SoundService so changes apply immediately. Store the settings in a table and save to DataStore on change or on leave, so the player's preferences persist across sessions. The settings rows use UIListLayout so adding a new setting is one Frame.
Play button: starting the game
The Play button is the menu's only job. When clicked, fire a RemoteEvent to the server to start the match, and play a confirmation animation so the player knows it registered. Hide the menu after the match starts, or fade it out if the game begins immediately. On game over, show the menu again with the updated state (best time, coins, level). Wire the Play button with a short press animation so it feels responsive.
Mobile menu considerations
Most Roblox players are on phones, so the menu must work at 375px width. Use Scale-based sizing for every element, make buttons at least 60x60 logical pixels for imprecise taps, and use big, readable fonts (28+ for titles, 18+ for buttons). Test the tab bar on mobile - if tabs are too small to tap reliably, increase their size or move them to the bottom where thumbs reach easily.