Roblox UI
Implement deliberate, editable Roblox UI that preserves the experience's visual language and
works across supported devices. Targets Roblox's rolling platform APIs. Pair with game-ui-ux
for engine-neutral information architecture and UX; this skill owns Roblox Instances and APIs.
When to use
- Use for authored
ScreenGui interfaces, Roblox layout/constraint APIs, UI lifecycle, input,
focus, animation, performance, or responsive/device fixes.
- Use to replace brittle pixel layouts, runtime-created Instance tangles, mouse-only menus, or
generic generated styling with a maintainable hierarchy.
When not to use: general UX flow and accessibility principles belong to game-ui-ux; raw
gameplay action mapping belongs to input-systems; server trust belongs to roblox-networking.
Workflow
- Inspect before designing. In Explorer, inventory existing
ScreenGuis, UI modules,
fonts, colors, icon family, spacing, corners, strokes, animation timing, naming, and reusable
components. Capture representative screenshots at two viewports. Extend that language.
- Define ownership and lifecycle.
StarterGui is the authored template; Roblox clones it
into each player's PlayerGui. Decide whether each ScreenGui should persist across respawn
with ResetOnSpawn = false; never keep stale references to a destroyed clone.
- Author a readable hierarchy. Prefer meaningful editable Instances and small behavior
modules. A normal screen should be understandable in Explorer; procedural generation is for
genuinely data-driven repetition, not the whole shell.
- Build responsive structure. Combine
UDim2 scale for placement with bounded offsets,
correct AnchorPoints, layouts, padding, content sizing, and constraints. Choose explicit
narrow/wide arrangements instead of shrinking a desktop panel until it technically fits.
- Handle platform obstructions. Keep interactive UI in
CoreUISafeInsets (the recommended
ScreenGui.ScreenInsets default), and account for touch controls and TV distance/overscan.
- Wire state and input. Render from a small view state; update on events. Use
Activated for
GuiButtons, explicit selected states, and reachable gamepad navigation. Bind non-widget
actions with ContextActionService only while the screen owns them.
- Polish with restraint. Implement hover, pressed, selected, and disabled states. Animate
state changes, not decoration; honor
GuiService.ReducedMotionEnabled and clean up tweens and
connections when the screen closes or is replaced.
- Verify the real interface. Use Device Emulator and Controller Emulator. Test desktop,
narrow portrait, mobile landscape, tablet-like, and console/gamepad; then respawn. Record
clipping, overlap, text, scroll, safe-area, focus path, touch target, and lifecycle results.
Production structure
StarterGui
└── InventoryGui (ScreenGui; ResetOnSpawn = false)
├── Root (Frame)
│ ├── Header
│ ├── Content
│ │ ├── CategoryList
│ │ ├── ItemGrid (ScrollingFrame)
│ │ └── DetailsPanel
│ └── Footer
└── InventoryController (LocalScript)
ReplicatedStorage
└── UI
├── Components
└── Theme
Names describe roles, not colors or temporary positions. Keep repeated item cells as templates or
components, but keep the authored shell visible in Explorer.
Patterns
Layout that survives content and viewport changes
-- Authored properties on ItemGrid (ScrollingFrame):
-- AutomaticCanvasSize = Enum.AutomaticSize.Y
-- CanvasSize = UDim2.fromOffset(0, 0)
-- Children: UIPadding + UIGridLayout
local grid = itemGrid.UIGridLayout
local projectBreakpoint = 700 -- derive from where this composition stops being usable
local function applyComposition()
local narrow = root.AbsoluteSize.X < projectBreakpoint
categoryList.UIListLayout.FillDirection = if narrow
then Enum.FillDirection.Horizontal
else Enum.FillDirection.Vertical
categoryList.Size = if narrow
then UDim2.new(1, 0, 0, 40)
else UDim2.new(0, 148, 1, 0)
itemGrid.Position = if narrow then UDim2.fromOffset(0, 52) else UDim2.fromOffset(164, 0)
itemGrid.Size = if narrow
then UDim2.new(1, 0, 1, -52)
else UDim2.new(0.58, -164, 1, 0)
detailsPanel.Visible = not narrow -- open details as a separate narrow-screen state
local width = itemGrid.AbsoluteSize.X
local columns = if width < 520 then 2 elseif width < 820 then 3 else 4
local gap = 12
local usable = width - itemGrid.UIPadding.PaddingLeft.Offset
- itemGrid.UIPadding.PaddingRight.Offset - gap * (columns - 1)
grid.CellSize = UDim2.fromOffset(math.floor(usable / columns), 112)
grid.CellPadding = UDim2.fromOffset(gap, gap)
end
root:GetPropertyChangedSignal("AbsoluteSize"):Connect(applyComposition)
applyComposition()
Use UIListLayout for one-dimensional flow, UIGridLayout for uniform cells, and
UITableLayout only when row/column alignment is truly tabular. AutomaticSize fits content;
AutomaticCanvasSize fits scroll content. Bound extremes with UISizeConstraint,
UIAspectRatioConstraint, or UITextSizeConstraint rather than one global UIScale guess.
One activation path and explicit gamepad entry
local GuiService = game:GetService("GuiService")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local function openInventory()
root.Visible = true
task.spawn(function()
RunService.RenderStepped:Wait() -- selection must be rendered and selectable
if root.Visible and (UserInputService.GamepadEnabled
or (UserInputService.KeyboardEnabled and not UserInputService.TouchEnabled)) then
GuiService.SelectedObject = firstItemButton
end
end)
end
local function closeInventory()
GuiService.SelectedObject = nil
root.Visible = false
end
firstItemButton.Activated:Connect(function()
selectItem(firstItemButton:GetAttribute("ItemId"))
end)
Prefer Activated over separate mouse/touch handlers. Set NextSelectionUp/Down/Left/Right
when automatic navigation is ambiguous, especially around grids, sidebars, and modals. A modal
must trap focus inside itself and restore a sensible selection when it closes.
Inspect Roblox's default selection adornment in context. If it overwhelms the interface, assign a
project-owned SelectionImageObject or selected-state treatment; keep it visible, valid, and
restrained rather than disabling focus feedback.
State transition without animation noise
local TweenService = game:GetService("TweenService")
local GuiService = game:GetService("GuiService")
local fade = TweenInfo.new(0.14, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
local function setPanelVisible(visible: boolean)
panel.Visible = true -- panel is a CanvasGroup
if GuiService.ReducedMotionEnabled then
panel.GroupTransparency = if visible then 0 else 1
else
TweenService:Create(panel, fade, {
GroupTransparency = if visible then 0 else 1,
}):Play()
end
if not visible then
task.delay(fade.Time, function()
if panel.GroupTransparency == 1 then panel.Visible = false end
end)
end
end
Animate a panel or state boundary once. Do not independently bounce every descendant.
Visual quality gates
- Establish hierarchy through size, weight, contrast, grouping, and whitespace before adding
decoration. Use a small spacing scale and align to it.
- Preserve the game's fonts, palette, icons, radii, and strokes. If none exist, define a compact
token module; do not improvise per component.
- Use corners where the material/form calls for them, not on every nested
Frame. Avoid nested
card-on-card shells, universal pills, arbitrary gradients/glows/shadows, neon outlines,
excessive transparency/blur, oversized headings, meaningless icons, and huge empty panels.
- Buttons with different importance should not look identical. Define primary, secondary,
quiet, destructive, selected, pressed, hover, and disabled treatment only as needed.
- Motion communicates open/close, focus, confirmation, or spatial relationship. Keep it short,
interruptible, and subtle; avoid perpetual motion and decorative tween chains.
- On touch devices, reserve the default movement/jump corners. Move or recompose bottom actions
that overlap them, and hide keyboard/gamepad hints when those inputs are not active.
Common failures
| Symptom |
Likely cause |
Remedy |
| UI disappears or duplicates after respawn |
wrong ResetOnSpawn policy or stale clone |
choose policy; reacquire from PlayerGui; clean old bindings |
| Phone layout clips |
desktop offsets/global scale used as layout |
add narrow composition, layouts, bounds, and Device Emulator checks |
| Content hidden under topbar/notch |
insets ignored or ScreenInsets = None |
use CoreUISafeInsets for interactive UI; inspect intentional bleed |
| Scroll ends early |
fixed CanvasSize |
use AutomaticCanvasSize or layout AbsoluteContentSize |
| Controller gets stuck |
no entry selection or bad neighbor graph |
set initial SelectedObject; define and test focus edges/modal trap |
| UI looks generated |
decoration substituted for hierarchy |
remove effects; align, reduce nesting, fix density and component roles |
| Explorer contains only one script |
entire authored interface is procedural |
author the shell as named Instances; generate repeated content only |
| Increasing connection/tween count |
screen rebinds without cleanup |
own connections per screen lifecycle; disconnect/cancel on teardown |
Resources
- Read
references/responsive-layout.md for layout decisions, inset policy, density, and the
required viewport matrix.
- Read
references/input-navigation.md when implementing gamepad/touch focus, modal behavior,
action binding, or motion preferences.
- Build
assets/inventory-quality-test.project.json with Rojo (it maps the adjacent JSON model)
for an editable Instance fixture used in visual/layout review; it is a test baseline, not a style
template for a shipping game.
Related skills
game-ui-ux — information hierarchy, screen flow, accessibility, and UX validation.
input-systems — gameplay action mapping and device switching.
roblox-networking — server-authoritative requests initiated by UI.
roblox-studio-workflow — Explorer-first editing and Studio verification.
Primary references
- Roblox Creator Hub:
https://create.roblox.com/docs/ui/on-screen-containers
- Roblox Creator Hub:
https://create.roblox.com/docs/ui/position-and-size
- Roblox Creator Hub:
https://create.roblox.com/docs/ui/size-modifiers
- Roblox Creator Hub:
https://create.roblox.com/docs/studio/testing-modes
1---2name: roblox-ui3description: Build production Roblox interfaces with ScreenGui/PlayerGui lifecycle, responsive UDim2 layouts, safe insets, reusable editable Instances, cross-device input and selection, restrained motion, cleanup, and multi-viewport verification. Use for Roblox HUDs, inventories, shops, settings, ability bars, ScrollingFrames, AutomaticSize, GuiService, gamepad focus, touch UI, or UI that clips, resets on respawn, looks generic, or was generated as one giant LocalScript.4---5
6# Roblox UI
7
8Implement deliberate, editable Roblox UI that preserves the experience's visual language and
9works across supported devices. Targets Roblox's rolling platform APIs. Pair with `game-ui-ux`
10for engine-neutral information architecture and UX; this skill owns Roblox Instances and APIs.
11
12## When to use
13
14- Use for authored `ScreenGui` interfaces, Roblox layout/constraint APIs, UI lifecycle, input,
15 focus, animation, performance, or responsive/device fixes.
16- Use to replace brittle pixel layouts, runtime-created Instance tangles, mouse-only menus, or
17 generic generated styling with a maintainable hierarchy.
18
19**When not to use:** general UX flow and accessibility principles belong to `game-ui-ux`; raw
20gameplay action mapping belongs to `input-systems`; server trust belongs to `roblox-networking`.
21
22## Workflow
23
241. **Inspect before designing.** In Explorer, inventory existing `ScreenGui`s, UI modules,
25 fonts, colors, icon family, spacing, corners, strokes, animation timing, naming, and reusable
26 components. Capture representative screenshots at two viewports. Extend that language.
272. **Define ownership and lifecycle.** `StarterGui` is the authored template; Roblox clones it
28 into each player's `PlayerGui`. Decide whether each `ScreenGui` should persist across respawn
29 with `ResetOnSpawn = false`; never keep stale references to a destroyed clone.
303. **Author a readable hierarchy.** Prefer meaningful editable Instances and small behavior
31 modules. A normal screen should be understandable in Explorer; procedural generation is for
32 genuinely data-driven repetition, not the whole shell.
334. **Build responsive structure.** Combine `UDim2` scale for placement with bounded offsets,
34 correct `AnchorPoint`s, layouts, padding, content sizing, and constraints. Choose explicit
35 narrow/wide arrangements instead of shrinking a desktop panel until it technically fits.
365. **Handle platform obstructions.** Keep interactive UI in `CoreUISafeInsets` (the recommended
37 `ScreenGui.ScreenInsets` default), and account for touch controls and TV distance/overscan.
386. **Wire state and input.** Render from a small view state; update on events. Use `Activated` for
39 `GuiButton`s, explicit selected states, and reachable gamepad navigation. Bind non-widget
40 actions with `ContextActionService` only while the screen owns them.
417. **Polish with restraint.** Implement hover, pressed, selected, and disabled states. Animate
42 state changes, not decoration; honor `GuiService.ReducedMotionEnabled` and clean up tweens and
43 connections when the screen closes or is replaced.
448. **Verify the real interface.** Use Device Emulator and Controller Emulator. Test desktop,
45 narrow portrait, mobile landscape, tablet-like, and console/gamepad; then respawn. Record
46 clipping, overlap, text, scroll, safe-area, focus path, touch target, and lifecycle results.
47
48## Production structure
49
50```text
51StarterGui
52└── InventoryGui (ScreenGui; ResetOnSpawn = false)
53 ├── Root (Frame)
54 │ ├── Header
55 │ ├── Content
56 │ │ ├── CategoryList
57 │ │ ├── ItemGrid (ScrollingFrame)
58 │ │ └── DetailsPanel
59 │ └── Footer
60 └── InventoryController (LocalScript)
61ReplicatedStorage
62└── UI
63 ├── Components
64 └── Theme
65```
66
67Names describe roles, not colors or temporary positions. Keep repeated item cells as templates or
68components, but keep the authored shell visible in Explorer.
69
70## Patterns
71
72### Layout that survives content and viewport changes
73
74```lua
75-- Authored properties on ItemGrid (ScrollingFrame):
76-- AutomaticCanvasSize = Enum.AutomaticSize.Y
77-- CanvasSize = UDim2.fromOffset(0, 0)
78-- Children: UIPadding + UIGridLayout
79local grid = itemGrid.UIGridLayout
80local projectBreakpoint = 700 -- derive from where this composition stops being usable
81
82local function applyComposition()
83 local narrow = root.AbsoluteSize.X < projectBreakpoint
84 categoryList.UIListLayout.FillDirection = if narrow
85 then Enum.FillDirection.Horizontal
86 else Enum.FillDirection.Vertical
87 categoryList.Size = if narrow
88 then UDim2.new(1, 0, 0, 40)
89 else UDim2.new(0, 148, 1, 0)
90 itemGrid.Position = if narrow then UDim2.fromOffset(0, 52) else UDim2.fromOffset(164, 0)
91 itemGrid.Size = if narrow
92 then UDim2.new(1, 0, 1, -52)
93 else UDim2.new(0.58, -164, 1, 0)
94 detailsPanel.Visible = not narrow -- open details as a separate narrow-screen state
95
96 local width = itemGrid.AbsoluteSize.X
97 local columns = if width < 520 then 2 elseif width < 820 then 3 else 4
98 local gap = 12
99 local usable = width - itemGrid.UIPadding.PaddingLeft.Offset
100 - itemGrid.UIPadding.PaddingRight.Offset - gap * (columns - 1)
101 grid.CellSize = UDim2.fromOffset(math.floor(usable / columns), 112)
102 grid.CellPadding = UDim2.fromOffset(gap, gap)
103end
104
105root:GetPropertyChangedSignal("AbsoluteSize"):Connect(applyComposition)
106applyComposition()
107```
108
109Use `UIListLayout` for one-dimensional flow, `UIGridLayout` for uniform cells, and
110`UITableLayout` only when row/column alignment is truly tabular. `AutomaticSize` fits content;
111`AutomaticCanvasSize` fits scroll content. Bound extremes with `UISizeConstraint`,
112`UIAspectRatioConstraint`, or `UITextSizeConstraint` rather than one global `UIScale` guess.
113
114### One activation path and explicit gamepad entry
115
116```lua
117local GuiService = game:GetService("GuiService")
118local RunService = game:GetService("RunService")
119local UserInputService = game:GetService("UserInputService")
120
121local function openInventory()
122 root.Visible = true
123 task.spawn(function()
124 RunService.RenderStepped:Wait() -- selection must be rendered and selectable
125 if root.Visible and (UserInputService.GamepadEnabled
126 or (UserInputService.KeyboardEnabled and not UserInputService.TouchEnabled)) then
127 GuiService.SelectedObject = firstItemButton
128 end
129 end)
130end
131
132local function closeInventory()
133 GuiService.SelectedObject = nil
134 root.Visible = false
135end
136
137firstItemButton.Activated:Connect(function()
138 selectItem(firstItemButton:GetAttribute("ItemId"))
139end)
140```
141
142Prefer `Activated` over separate mouse/touch handlers. Set `NextSelectionUp/Down/Left/Right`
143when automatic navigation is ambiguous, especially around grids, sidebars, and modals. A modal
144must trap focus inside itself and restore a sensible selection when it closes.
145
146Inspect Roblox's default selection adornment in context. If it overwhelms the interface, assign a
147project-owned `SelectionImageObject` or selected-state treatment; keep it visible, valid, and
148restrained rather than disabling focus feedback.
149
150### State transition without animation noise
151
152```lua
153local TweenService = game:GetService("TweenService")
154local GuiService = game:GetService("GuiService")
155local fade = TweenInfo.new(0.14, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
156
157local function setPanelVisible(visible: boolean)
158 panel.Visible = true -- panel is a CanvasGroup
159 if GuiService.ReducedMotionEnabled then
160 panel.GroupTransparency = if visible then 0 else 1
161 else
162 TweenService:Create(panel, fade, {
163 GroupTransparency = if visible then 0 else 1,
164 }):Play()
165 end
166 if not visible then
167 task.delay(fade.Time, function()
168 if panel.GroupTransparency == 1 then panel.Visible = false end
169 end)
170 end
171end
172```
173
174Animate a panel or state boundary once. Do not independently bounce every descendant.
175
176## Visual quality gates
177
178- Establish hierarchy through size, weight, contrast, grouping, and whitespace before adding
179 decoration. Use a small spacing scale and align to it.
180- Preserve the game's fonts, palette, icons, radii, and strokes. If none exist, define a compact
181 token module; do not improvise per component.
182- Use corners where the material/form calls for them, not on every nested `Frame`. Avoid nested
183 card-on-card shells, universal pills, arbitrary gradients/glows/shadows, neon outlines,
184 excessive transparency/blur, oversized headings, meaningless icons, and huge empty panels.
185- Buttons with different importance should not look identical. Define primary, secondary,
186 quiet, destructive, selected, pressed, hover, and disabled treatment only as needed.
187- Motion communicates open/close, focus, confirmation, or spatial relationship. Keep it short,
188 interruptible, and subtle; avoid perpetual motion and decorative tween chains.
189- On touch devices, reserve the default movement/jump corners. Move or recompose bottom actions
190 that overlap them, and hide keyboard/gamepad hints when those inputs are not active.
191
192## Common failures
193
194| Symptom | Likely cause | Remedy |
195|---|---|---|
196| UI disappears or duplicates after respawn | wrong `ResetOnSpawn` policy or stale clone | choose policy; reacquire from `PlayerGui`; clean old bindings |
197| Phone layout clips | desktop offsets/global scale used as layout | add narrow composition, layouts, bounds, and Device Emulator checks |
198| Content hidden under topbar/notch | insets ignored or `ScreenInsets = None` | use `CoreUISafeInsets` for interactive UI; inspect intentional bleed |
199| Scroll ends early | fixed `CanvasSize` | use `AutomaticCanvasSize` or layout `AbsoluteContentSize` |
200| Controller gets stuck | no entry selection or bad neighbor graph | set initial `SelectedObject`; define and test focus edges/modal trap |
201| UI looks generated | decoration substituted for hierarchy | remove effects; align, reduce nesting, fix density and component roles |
202| Explorer contains only one script | entire authored interface is procedural | author the shell as named Instances; generate repeated content only |
203| Increasing connection/tween count | screen rebinds without cleanup | own connections per screen lifecycle; disconnect/cancel on teardown |
204
205## Resources
206
207- Read `references/responsive-layout.md` for layout decisions, inset policy, density, and the
208 required viewport matrix.
209- Read `references/input-navigation.md` when implementing gamepad/touch focus, modal behavior,
210 action binding, or motion preferences.
211- Build `assets/inventory-quality-test.project.json` with Rojo (it maps the adjacent JSON model)
212 for an editable Instance fixture used in visual/layout review; it is a test baseline, not a style
213 template for a shipping game.
214
215## Related skills
216
217- `game-ui-ux` — information hierarchy, screen flow, accessibility, and UX validation.
218- `input-systems` — gameplay action mapping and device switching.
219- `roblox-networking` — server-authoritative requests initiated by UI.
220- `roblox-studio-workflow` — Explorer-first editing and Studio verification.
221
222## Primary references
223
224- Roblox Creator Hub: `https://create.roblox.com/docs/ui/on-screen-containers`
225- Roblox Creator Hub: `https://create.roblox.com/docs/ui/position-and-size`
226- Roblox Creator Hub: `https://create.roblox.com/docs/ui/size-modifiers`
227- Roblox Creator Hub: `https://create.roblox.com/docs/studio/testing-modes`