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
ScreenGuiinterfaces, Roblox layout/constraint APIs, UI lifecycle, input, - Use to replace brittle pixel layouts, runtime-created Instance tangles, mouse-only menus, or
focus, animation, performance, or responsive/device fixes.
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, - Define ownership and lifecycle.
StarterGuiis the authored template; Roblox clones it - Author a readable hierarchy. Prefer meaningful editable Instances and small behavior
- Build responsive structure. Combine
UDim2scale for placement with bounded offsets, - Handle platform obstructions. Keep interactive UI in
CoreUISafeInsets(the recommended - Wire state and input. Render from a small view state; update on events. Use
Activatedfor - Polish with restraint. Implement hover, pressed, selected, and disabled states. Animate
- Verify the real interface. Use Device Emulator and Controller Emulator. Test desktop,
fonts, colors, icon family, spacing, corners, strokes, animation timing, naming, and reusable components. Capture representative screenshots at two viewports. Extend that language.
into each player's PlayerGui. Decide whether each ScreenGui should persist across respawn with ResetOnSpawn = false; never keep stale references to a destroyed clone.
modules. A normal screen should be understandable in Explorer; procedural generation is for genuinely data-driven repetition, not the whole shell.
correct AnchorPoints, layouts, padding, content sizing, and constraints. Choose explicit narrow/wide arrangements instead of shrinking a desktop panel until it technically fits.
ScreenGui.ScreenInsets default), and account for touch controls and TV distance/overscan.
GuiButtons, explicit selected states, and reachable gamepad navigation. Bind non-widget actions with ContextActionService only while the screen owns them.
state changes, not decoration; honor GuiService.ReducedMotionEnabled and clean up tweens and connections when the screen closes or is replaced.
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
- Preserve the game's fonts, palette, icons, radii, and strokes. If none exist, define a compact
- Use corners where the material/form calls for them, not on every nested
Frame. Avoid nested - Buttons with different importance should not look identical. Define primary, secondary,
- Motion communicates open/close, focus, confirmation, or spatial relationship. Keep it short,
- On touch devices, reserve the default movement/jump corners. Move or recompose bottom actions
decoration. Use a small spacing scale and align to it.
token module; do not improvise per component.
card-on-card shells, universal pills, arbitrary gradients/glows/shadows, neon outlines, excessive transparency/blur, oversized headings, meaningless icons, and huge empty panels.
quiet, destructive, selected, pressed, hover, and disabled treatment only as needed.
interruptible, and subtle; avoid perpetual motion and decorative tween chains.
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.mdfor layout decisions, inset policy, density, and the - Read
references/input-navigation.mdwhen implementing gamepad/touch focus, modal behavior, - Build
assets/inventory-quality-test.project.jsonwith Rojo (it maps the adjacent JSON model)
required viewport matrix.
action binding, or motion preferences.
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