Roblox Settings Menu Template: Volume, Graphics, Keybinds (2026)
2026-07-21 · Roblox GUI Maker Team
Settings menu structure with UIListLayout
Build the settings menu as a single Frame with a UIListLayout inside. Each row is a setting: a label on the left and a control (slider or toggle) on the right. UIListLayout stacks the rows automatically with consistent spacing, so adding a new setting is one Frame. The menu opens from a gear button in the corner and closes with Escape or a close button. Use Scale-based sizing so the menu fits any screen, and put a semi-transparent backdrop behind it that closes the menu when clicked.
Volume sliders with live SoundService binding
The volume slider is a draggable handle on a track. When the player drags it, update SoundService.AudioEffects or your custom audio groups immediately - no server round-trip needed. The slider value is a ratio (0 to 1) that maps to volume (0 to 100%). Show the current value as a percentage next to the slider. Save the value to a settings table so it can be persisted to DataStore on change or on leave.
1-- Settings Menu - Client Script2-- Place in: StarterGui (LocalScript)34local Players = game:GetService("Players")5local SoundService = game:GetService("SoundService")6local ReplicatedStorage = game:GetService("ReplicatedStorage")78local player = Players.LocalPlayer9local gui = script.Parent10local settings = { masterVolume = 0.5, musicOn = true, quality = "Auto" }1112local saveEvent = ReplicatedStorage:WaitForChild("SaveSettings")1314-- Volume slider: bind to SoundService live15local volumeSlider = gui:WaitForChild("VolumeSlider")16local volumeHandle = volumeSlider:WaitForChild("Handle")17local volumeLabel = volumeSlider:WaitForChild("ValueLabel")1819local function setVolume(ratio)20 settings.masterVolume = ratio21 SoundService.Volume = ratio22 volumeLabel.Text = math.floor(ratio * 100) .. "%"23 volumeHandle.Position = UDim2.new(ratio, 0, 0.5, 0)24 saveEvent:FireServer(settings)25end2627-- Drag the handle28local dragging = false29volumeHandle.MouseButton1Down:Connect(function() dragging = true end)30game:GetService("UserInputService").InputEnded:Connect(function()31 if dragging then dragging = false end32end)33volumeSlider.MouseMoved:Connect(function(x)34 if dragging then35 local rel = math.clamp((x - volumeSlider.AbsolutePosition.X) / volumeSlider.AbsoluteSize.X, 0, 1)36 setVolume(rel)37 end38end)
Graphics and quality toggles
Graphics settings let players trade visual quality for performance. A quality dropdown (Auto/Low/Medium/High) maps to GraphicsQualityLevel in GameSettings, or to your own lighting and shadow toggles if you manage them manually. Show the current quality as the dropdown label, and apply it immediately on change. On mobile, default to Auto or Low so the game runs smoothly on lower-end devices.
Keybind settings
Keybinds let players remap actions to their preferred keys. Store keybinds as a table mapping action names to KeyCode strings, and show each as a row with the action name and its current key. When the player clicks a key row, wait for the next key press and assign it. Save keybinds to DataStore with the rest of the settings. Common keybinds include inventory toggle, sprint, jump, and menu open.
Save settings with DataStore
Settings mean nothing if they reset on rejoin. Save the settings table to DataStore whenever the player changes a value, and load it on join to restore their preferences. Wrap DataStore calls in pcall so a failed save does not crash the game, and save on PlayerRemoving as a final backup. On join, apply the loaded values to every slider, toggle, and keybind row so the menu shows the player's saved state.
1-- Settings Menu - Server Script2-- Place in: ServerScriptService (Script)34local Players = game:GetService("Players")5local DataStoreService = game:GetService("DataStoreService")6local ReplicatedStorage = game:GetService("ReplicatedStorage")78local settingsStore = DataStoreService:GetDataStore("PlayerSettings")9local saveEvent = ReplicatedStorage:WaitForChild("SaveSettings")10local loadEvent = ReplicatedStorage:WaitForChild("LoadSettings")1112local playerSettings = {}1314-- Load settings on join15local function loadSettings(player)16 local success, data = pcall(function()17 return settingsStore:GetAsync(player.UserId)18 end)19 playerSettings[player.UserId] = (success and data) or {}20 loadEvent:FireClient(player, playerSettings[player.UserId])21end2223-- Save settings24saveEvent.OnServerEvent:Connect(function(player, settings)25 playerSettings[player.UserId] = settings26 pcall(function()27 settingsStore:SetAsync(player.UserId, settings)28 end)29end)3031Players.PlayerAdded:Connect(loadSettings)32Players.PlayerRemoving:Connect(function(player)33 if playerSettings[player.UserId] then34 pcall(function()35 settingsStore:SetAsync(player.UserId, playerSettings[player.UserId])36 end)37 end38end)