Quick answer: Godot's default focus navigation uses spatial proximity, not scene tree order. When you press Tab, Godot moves focus to the nearest focusable control in the 'next' direction. If your controls are not spatially arranged in the order you expect, focus will jump to unexpected controls.
Here is how to fix Godot focus order wrong tabbing UI. You have a menu with buttons arranged vertically — Play, Options, Quit — and when the player presses Tab or a D-pad direction, focus jumps to the wrong button. Instead of moving from Play to Options, it jumps to Quit, or to a button on a completely different panel. Focus navigation in Godot 4 uses spatial proximity by default, not scene tree order, and that trips up almost everyone building keyboard or gamepad-driven UI.
The Symptom
When navigating your UI with the Tab key, arrow keys, or gamepad D-pad, focus does not move through controls in the order you expect. Pressing Down on a vertical list might skip a button or jump to a different column. Pressing Tab might cycle through controls in what seems like random order. In some cases, pressing a direction key causes focus to get “stuck” on a control with no obvious way to navigate away from it.
This is especially problematic for gamepad support, where players have no mouse to fall back on. A broken focus chain means the player literally cannot reach certain buttons or menu items, making parts of your UI inaccessible.
What Causes This
Godot 4’s default focus navigation algorithm works by spatial distance. When you press a directional input, the engine finds the nearest focusable Control node in that direction based on screen position. Tab uses the focus_next property, which defaults to empty — meaning Godot falls back to an internal ordering heuristic that considers both position and scene tree order but does not always produce intuitive results.
Several factors make the spatial algorithm produce unexpected results. Controls that overlap or are very close together can confuse the nearest-neighbor calculation. Controls in different containers at the same vertical position might be prioritized over the control directly below the current one. Hidden controls with visible = false are correctly skipped, but controls with zero opacity or those off-screen are still considered focusable.
Another common cause is forgetting to set focus_mode on controls that should be focusable. By default, only Button, LineEdit, TextEdit, and similar interactive controls have focus_mode set to FOCUS_ALL. If you have a custom Control that should participate in focus navigation, you must enable it manually.
The Fix
Step 1: Explicitly set focus_next and focus_previous for Tab navigation. Instead of relying on Godot’s heuristic, define the exact Tab order by assigning node paths:
# In the Inspector, set these on each button:
# PlayButton.focus_next = NodePath to OptionsButton
# OptionsButton.focus_next = NodePath to QuitButton
# QuitButton.focus_next = NodePath to PlayButton (loop)
# Or set them via code:
@onready var play_btn = $VBox/PlayButton
@onready var opts_btn = $VBox/OptionsButton
@onready var quit_btn = $VBox/QuitButton
func _ready():
play_btn.focus_next = play_btn.get_path_to(opts_btn)
play_btn.focus_previous = play_btn.get_path_to(quit_btn)
opts_btn.focus_next = opts_btn.get_path_to(quit_btn)
opts_btn.focus_previous = opts_btn.get_path_to(play_btn)
quit_btn.focus_next = quit_btn.get_path_to(play_btn)
quit_btn.focus_previous = quit_btn.get_path_to(opts_btn)
Step 2: Set directional focus neighbors for arrow key and D-pad navigation. The focus_neighbor_* properties control what happens when the player presses Up, Down, Left, or Right:
# Set directional neighbors for a vertical menu
func _setup_focus_neighbors():
# Play button: down goes to Options, up wraps to Quit
play_btn.focus_neighbor_bottom = play_btn.get_path_to(opts_btn)
play_btn.focus_neighbor_top = play_btn.get_path_to(quit_btn)
# Options button: down goes to Quit, up goes to Play
opts_btn.focus_neighbor_bottom = opts_btn.get_path_to(quit_btn)
opts_btn.focus_neighbor_top = opts_btn.get_path_to(play_btn)
# Quit button: down wraps to Play, up goes to Options
quit_btn.focus_neighbor_bottom = quit_btn.get_path_to(play_btn)
quit_btn.focus_neighbor_top = quit_btn.get_path_to(opts_btn)
Step 3: Set initial focus with grab_focus(). When a menu scene loads, no control has focus by default. Keyboard and gamepad users cannot navigate until something is focused. Call grab_focus() on the first control:
func _ready():
_setup_focus_neighbors()
# Give initial focus to the first button
play_btn.grab_focus()
Step 4: Handle dynamic UI and scene transitions. When switching between menu panels or showing/hiding UI elements, you need to re-establish focus on the newly visible panel:
func _on_options_pressed():
$MainMenu.visible = false
$OptionsPanel.visible = true
# Grab focus on the first control in the new panel
$OptionsPanel/VolumeSlider.grab_focus()
func _on_back_pressed():
$OptionsPanel.visible = false
$MainMenu.visible = true
# Return focus to the Options button we came from
opts_btn.grab_focus()
Why This Works
The focus_next, focus_previous, and focus_neighbor_* properties are explicit overrides that bypass Godot’s spatial focus algorithm entirely. When any of these properties contains a valid NodePath, Godot uses that path directly instead of searching for the nearest focusable control. This gives you deterministic, predictable navigation regardless of where controls are positioned on screen.
The grab_focus() method sets the Control as the viewport’s current focus owner. Godot tracks focus per viewport, so calling grab_focus() both sets the internal focus state and emits the focus_entered signal on the target control, which triggers visual feedback like button highlight states. Without this initial call, the focus system is in a null state where directional inputs have no starting point to navigate from.
For dynamic UI, maintaining focus across visibility changes is critical because Godot does not automatically transfer focus when a focused control becomes invisible. If the player is focused on a button that gets hidden, focus is lost entirely, and the player is stuck until they click something with a mouse.
Related Issues
If your UI controls receive focus correctly but mouse clicks are also passing through to the game world behind the UI, see our guide on fixing UI clicks passing through to the game world. The mouse_filter property interacts with focus in important ways.
For scrollable content where focused items should automatically scroll into view, check our post on fixing ScrollContainer scrolling. ScrollContainer has built-in support for following focus, but it needs to be configured correctly.
If you are building an accessible UI with RichTextLabel-based interactive elements, our guide on fixing BBCode rendering covers how URL tags in RichTextLabel interact with the focus system.
Explicit focus paths. Initial grab_focus. Predictable navigation.