We are excited to announce a series of new keybinds we are adding to the Roblox client to support keyboard navigation, out of the box, in all Experiences, with little to no additional effort required by developers !
The \ (Backslash) key will now toggle UI Selection
If you do not have an element selected, it selects an element in the PlayerGui.
If you have one selected, it will unselect it.
This is equivalent to the behavior of a controller’s Select button, and it also respects AutoSelectGuiEnabled. We’ve provided the logic for this in the following code block for reference.
local function EnableKeyboardNavigation(actionName, inputState, inputObject) if inputState ~= Enum.UserInputState.Begin then return Enum.ContextActionResult.Pass end -- Respect AutoSelectGuiEnabled if not GuiService.AutoSelectGuiEnabled then return Enum.ContextActionResult.Pass end -- There is a selected object, unselect it if GuiService.SelectedObject then GuiService.SelectedObject = nil return Enum.ContextActionResult.Sink end -- Select an element in the PlayerGui GuiService:Select(PlayerGui) return Enum.ContextActionResult.Sink end ContextActionService:BindAction("EnableKeyboardUINavigation", EnableKeyboardNavigation, false, Enum.KeyCode.BackSlash)
This addition is now shown in the Controls section of the In-Game Menu as UI Selection Toggle under Misc.
PageUp / PageDown = Up / Down
- Fn+▲ / Fn+▼ on keyboards without dedicated keys
Home / End = Left / Right
- Fn+◄ / Fn+► on keyboards without dedicated keys
This is equivalent to a controller’s joystick behavior when focused on a scrolling frame. The code is available below for reference.
local function ScrollSelectedElement(actionName, inputState, inputObject) if inputState ~= Enum.UserInputState.Begin then return Enum.ContextActionResult.Pass end local selectedObject = GuiService.SelectedObject -- If no selected object, ignore if selectedObject == nil then return Enum.ContextActionResult.Pass end local scrollingFrame = nil if selectedObject.ClassName == "ScrollingFrame" then scrollingFrame = selectedObject else local scrollingFrameAncestor = selectedObject:FindFirstAncestorOfClass("ScrollingFrame") if scrollingFrameAncestor ~= nil then scrollingFrame = scrollingFrameAncestor end end -- If selected object is not a scrolling frame, or a descendant of one, ignore if scrollingFrame == nil then return Enum.ContextActionResult.Pass end local scrollDistance = 0 if inputObject.KeyCode == Enum.KeyCode.PageUp or inputObject.KeyCode == Enum.KeyCode.Home then scrollDistance = -100 elseif inputObject.KeyCode == Enum.KeyCode.PageDown or inputObject.KeyCode == Enum.KeyCode.End then scrollDistance = 100 end local x = scrollingFrame.CanvasPosition.X local y = scrollingFrame.CanvasPosition.Y if inputObject.KeyCode == Enum.KeyCode.PageUp or inputObject.KeyCode == Enum.KeyCode.PageDown then -- Scroll vertically y = math.max(0, math.min(y + scrollDistance, scrollingFrame.AbsoluteCanvasSize.Y)) elseif inputObject.KeyCode == Enum.KeyCode.Home or inputObject.KeyCode == Enum.KeyCode.End then -- Scroll horizontally x = math.max(0, math.min(x + scrollDistance, scrollingFrame.AbsoluteCanvasSize.X)) end scrollingFrame.CanvasPosition = Vector2.new(x, y) return Enum.ContextActionResult.Pass end ContextActionService:BindAction("ScrollSelectedElement", ScrollSelectedElement, false, Enum.KeyCode.PageUp, Enum.KeyCode.PageDown, Enum.KeyCode.Home, Enum.KeyCode.End)
You will note via the code blocks that the action for scrolling always passes input, while the toggle UI selection action sinks input when an event occurs (an element is selected or deselected). This is consistent with the implementation of other keybinds we have added (e.g. emotes menu), and allows you as the developer to override this functionality if you choose to by binding an action at a higher priority with BindActionAtPriority. Events bound at the C++ level (e.g. ProximityPrompts with any of these keys as the KeyboardKey) will be completely unaffected.
Players should now be able to navigate UI in experiences using only your keyboard. Once an element is selected, use the arrow keys (▲▼◄ ►) or WASD to navigate between elements, and Enter to activate. If you find any gaps in your experience / things you can’t do, please file a bug here on the DevForum - we’ll take a look at it.
Developers - there is very little difference between supporting controllers, and supporting keyboard navigation.
For any shortcuts you’ve added for gamepad controllers, consider adding one for keyboard users.
- E.g. If you allow users to close modals or menus by pressing B (recommended by the Xbox Featured Experience Guidelines), allowing keyboard users to do the same thing by pressing Backspace.
- Don’t leave users guessing how to do things - show them how to perform an action either in the context of which that action is performed, or in an easily-accessible menu. For more information or best practices, visit the GetStringForKeyCode section on the UserInputService docs page.
Use keyboard navigation as an easy way to validate general controller navigation, through your own definitions of selection groups and the gamepad selection algorithm. Read more about this here: Gamepad UI Highlight Improvements
If you find your experience working well with keyboard navigation
- Consider disabling Virtual Cursor either entirely, or conditionally.
- Navigating UI with buttons is generally a more preferable experience to navigation with joysticks for users with and without disabilities - it requires less fine motor control.
- Consider enabling for Xbox if you haven’t already, and you abide by other Xbox guidelines.
- Consider disabling Virtual Cursor either entirely, or conditionally.
Consider improving other areas of your game for better keyboard support
If you’re currently using the Backslash key for any actions in-experience, please assess any user workflows that might be affected, consider using another key in those cases, or bind your action to a higher priority and allow users to navigate their UI with a keyboard only via other methods. We analyzed the usage of this key and it’s extremely low.There should be very little impact to experiences.
If you have your own implementation of entering / exiting keyboard navigation, you can disable the existing action by adding an action bound to the same key at a higher priority that sinks input, or set AutoSelectGuiEnabled to
false. If you do either, consider adding Backslash as a key to whatever action you define for your own keyboard navigation, or ensuring that users are otherwise aware of how to navigate your UI with keyboard navigation.
Here’s an example of how you could customize the \ key to select a specific element on the screen, rather than what GuiService:Select() chooses automatically.
local function EnableKeyboardNavigation(actionName, inputState, inputObject) if inputState ~= Enum.UserInputState.Begin then return Enum.ContextActionResult.Pass end -- The selected object is already active, disable if GuiService.SelectedObject then GuiService.SelectedObject = nil return Enum.ContextActionResult.Sink -- Returning Pass instead of setting the SelectedObject to nil would also -- work, because the existing action would pass in this case as well. end GuiService.SelectedObject = cactusButton return Enum.ContextActionResult.Sink end ContextActionService:BindActionAtPriority("CustomEnableKeyboardNavigation", EnableKeyboardNavigation, false, 100, Enum.KeyCode.BackSlash)
Please leave any suggestions or feedback in the comments. We are working to provide better guidelines on controller keyboard navigation with documentation, stay tuned .