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.


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
local Players = game:GetService("Players")-- Get reference to player's jump buttonlocal player = Players.LocalPlayerlocal PlayerGui = player:WaitForChild("PlayerGui")local ScreenGui = PlayerGui:WaitForChild("ScreenGui")local TouchGui = PlayerGui:WaitForChild("TouchGui")local TouchControlFrame = TouchGui:WaitForChild("TouchControlFrame")local JumpButton = TouchControlFrame:WaitForChild("JumpButton")-- Get absolute size and position of buttonlocal absSizeX, absSizeY = JumpButton.AbsoluteSize.X, JumpButton.AbsoluteSize.Ylocal absPositionX, absPositionY = JumpButton.AbsolutePosition.X, JumpButton.AbsolutePosition.Y-- Create new button above jump buttonlocal customButton = Instance.new("ImageButton")customButton.Parent = ScreenGuicustomButton.AnchorPoint = Vector2.new(0.5, 1)customButton.Size = UDim2.new(0, absSizeX * 0.8, 0, absSizeY * 0.8)customButton.Position = UDim2.new(0, 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 accommodate the current input.
ModuleScript - Input Type Detection
local UserInputService = game:GetService("UserInputService")
local PlayerInput = {}
PlayerInput.InputTypes = {
KEYBOARD_MOUSE = "keyboard/mouse",
TOUCH = "touch",
GAMEPAD = "gamepad"
}
PlayerInput.CurrentInput = nil
PlayerInput.InputTypeChanged = Instance.new("BindableEvent")
local function getInputType(userInputType)
if userInputType == Enum.UserInputType.MouseButton1 or
userInputType == Enum.UserInputType.MouseButton2 or
userInputType == Enum.UserInputType.MouseButton3 or
userInputType == Enum.UserInputType.MouseWheel or
userInputType == Enum.UserInputType.MouseMovement or
userInputType == Enum.UserInputType.Keyboard then
return PlayerInput.InputTypes.KEYBOARD_MOUSE
elseif userInputType == Enum.UserInputType.Touch or
userInputType == Enum.UserInputType.Accelerometer or
userInputType == Enum.UserInputType.Gyro then
return PlayerInput.InputTypes.TOUCH
elseif userInputType == Enum.UserInputType.Gamepad1 or
userInputType == Enum.UserInputType.Gamepad2 or
userInputType == Enum.UserInputType.Gamepad3 or
userInputType == Enum.UserInputType.Gamepad4 or
userInputType == Enum.UserInputType.Gamepad5 or
userInputType == Enum.UserInputType.Gamepad6 or
userInputType == Enum.UserInputType.Gamepad7 or
userInputType == Enum.UserInputType.Gamepad8 then
return PlayerInput.InputTypes.GAMEPAD
end
return nil
end
local function setPlayerInputType(userInputType)
local playerInputType = getInputType(userInputType)
if playerInputType and playerInputType ~= PlayerInput.CurrentInput then
PlayerInput.CurrentInput = playerInputType
PlayerInput.InputTypeChanged:Fire(playerInputType)
end
end
-- Initially set the player's input type
setPlayerInputType(UserInputService:GetLastInputType())
-- Update current input type based on last input type received
UserInputService.LastInputTypeChanged:Connect(setPlayerInputType)
return PlayerInput
LocalScript - Check/Detect Current Input Type
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PlayerInput = require(ReplicatedStorage:WaitForChild("PlayerInput"))
-- Check the player's input type at any time by reading PlayerInput.CurrentInput
print("Player is using", PlayerInput.CurrentInput)
-- Listen for input type changes with PlayerInput.InputTypeChanged event
PlayerInput.InputTypeChanged.Event:Connect(function(newInputType)
print("Input changed to", newInputType)
end)