Cross-Platform Design

Roblox is inherently cross-platform as users can discover and join experiences on a PC, then later pick up their phone and continue where they left off. You should design your Roblox experiences to be accessible and enjoyable on all platforms that you choose to support, instead of optimizing for one platform and neglecting others.

UI Layout

Just because a UI fits perfectly on a PC screen doesn't mean it's as functional on smaller mobile screens. For example, the color customization tiles in this UI become small and cramped on a phone:

In contrast, a slight redesign of the UI menu offers a better user experience on both PC and phone:

Reserved Zones

On mobile devices, the default controls occupy a portion of the bottom-left and bottom-right corners of the screen. When you design an experience's UI, avoid placing important info or virtual buttons in these zones.

Thumb Zones

Most mobile users use two thumbs — one on the virtual thumbstick and one on the jump button. Depending on the physical size of the device and the user's hands, "reaching" too far from the bottom corners becomes uncomfortable or impossible, so you should avoid placing frequently-used buttons outside of easy-to-reach zones.

Remember that comfortable thumb zones differ between phones and tablets because tablets have a larger screen. A button placed 30% below the screen's top edge is reachable on a phone but almost unreachable on a tablet, so you should consider relational position over percentage-based.

Context-Based UI

Screen space is limited on mobile devices, so you should show only the most vital information during active gameplay. For example, if your experience includes a special input action to open doors and treasure chests, it doesn't make sense to constantly show an "Open" button on the screen. Instead, use a proximity prompt or similar method to accept input only when the character approaches a door or chest.

ProximityPrompt that automatically appears when character is near chest

Custom button that you display only when character is near chest

Adaptable UI

A cross-platform UI layout should be adaptable across all devices, not just between PC, mobile, and console but also between phones and tablets. You can achieve this through relational positioning and input type detection.

Positioning in Relation

A reliable approach for adaptable UI on both phones and tablets is to position custom buttons near frequently-used controls like the default jump button, placing them within easy reach of the user's right thumb.

The following code, placed in a LocalScript within StarterPlayerScripts, fetches the position of the jump button and creates a placeholder ImageButton above it.

LocalScript - Custom Button Above Jump Button

1local Players = game:GetService("Players")
3-- Get reference to player's jump button
4local player = Players.LocalPlayer
5local PlayerGui = player:WaitForChild("PlayerGui")
6local ScreenGui = PlayerGui:WaitForChild("ScreenGui")
7local TouchGui = PlayerGui:WaitForChild("TouchGui")
8local TouchControlFrame = TouchGui:WaitForChild("TouchControlFrame")
9local JumpButton = TouchControlFrame:WaitForChild("JumpButton")
11-- Get absolute size and position of button
12local absSizeX, absSizeY = JumpButton.AbsoluteSize.X, JumpButton.AbsoluteSize.Y
13local absPositionX, absPositionY = JumpButton.AbsolutePosition.X, JumpButton.AbsolutePosition.Y
15-- Create new button above jump button
16local customButton ="ImageButton")
17customButton.Parent = ScreenGui
18customButton.AnchorPoint =, 1)
19customButton.Size =, absSizeX * 0.8, 0, absSizeY * 0.8)
20customButton.Position =, absPositionX + (absSizeX / 2), 0, absPositionY - 20)

Adjusting by Input Type

Another approach for adaptable UI is to adjust your layout based on which input type the user is using, for example keyboard/mouse, touch, or gamepad.

The following ModuleScript determines the user's input type on join and detects changes to input type during gameplay. From a LocalScript that requires the module, you can detect the user's input type at any time and/or connect to the module's BindableEvent to detect input type changes. Upon detection, you can reposition UI elements to better accomodate the current input.

ModuleScript - Input Type Detection

1local UserInputService = game:GetService("UserInputService")
3local PlayerInput = {}
5PlayerInput.InputTypes = {
6 KEYBOARD_MOUSE = "keyboard/mouse",
7 TOUCH = "touch",
8 GAMEPAD = "gamepad"
11PlayerInput.CurrentInput = nil
12PlayerInput.InputTypeChanged ="BindableEvent")
14local function getInputType(userInputType)
15 if userInputType == Enum.UserInputType.MouseButton1 or
16 userInputType == Enum.UserInputType.MouseButton2 or
17 userInputType == Enum.UserInputType.MouseButton3 or
18 userInputType == Enum.UserInputType.MouseWheel or
19 userInputType == Enum.UserInputType.MouseMovement or
20 userInputType == Enum.UserInputType.Keyboard then
22 return PlayerInput.InputTypes.KEYBOARD_MOUSE
24 elseif userInputType == Enum.UserInputType.Touch or
25 userInputType == Enum.UserInputType.Accelerometer or
26 userInputType == Enum.UserInputType.Gyro then
28 return PlayerInput.InputTypes.TOUCH
30 elseif userInputType == Enum.UserInputType.Gamepad1 or
31 userInputType == Enum.UserInputType.Gamepad2 or
32 userInputType == Enum.UserInputType.Gamepad3 or
33 userInputType == Enum.UserInputType.Gamepad4 or
34 userInputType == Enum.UserInputType.Gamepad5 or
35 userInputType == Enum.UserInputType.Gamepad6 or
36 userInputType == Enum.UserInputType.Gamepad7 or
37 userInputType == Enum.UserInputType.Gamepad8 then
39 return PlayerInput.InputTypes.GAMEPAD
40 end
42 return nil
45local function setPlayerInputType(userInputType)
46 local playerInputType = getInputType(userInputType)
47 if playerInputType and playerInputType ~= PlayerInput.CurrentInput then
48 PlayerInput.CurrentInput = playerInputType
49 PlayerInput.InputTypeChanged:Fire(playerInputType)
50 end
53-- Initially set the player's input type
56-- Update current input type based on last input type received
59return PlayerInput
LocalScript - Check/Detect Current Input Type

1local ReplicatedStorage = game:GetService("ReplicatedStorage")
2local PlayerInput = require(ReplicatedStorage:WaitForChild("PlayerInput"))
4-- Check the player's input type at any time by reading PlayerInput.CurrentInput
5print("Player is using", PlayerInput.CurrentInput)
7-- Listen for input type changes with PlayerInput.InputTypeChanged event
9 print("Input changed to", newInputType)