Godot UI / Control nodes (4.x)
Lay out responsive UI with Control anchors and Container nodes, style it with a
Theme, and make it navigable by keyboard and gamepad. Targets Godot 4.3+.
When to Use
- Building HUDs, menus, inventories, dialog boxes, or settings screens with
Control-derived nodes. - Arranging UI that must adapt to window size changes.
- Creating or applying a
Themeresource for consistent styling. - Wiring focus navigation for controller/keyboard input.
- Working inside a
.tscnthat containsControl,Container,Theme, or focus properties.
Trigger keywords: Control, Container, VBoxContainer, HBoxContainer, GridContainer, MarginContainer, anchors, offsets, size_flags, Theme, StyleBox, focus, grab_focus, CanvasLayer, HUD, menu, UI layout.
When not to use:
- In-world 2D nodes (
Node2D/sprites) →godot-nodes-scenes. - Animating UI transitions →
godot-animation(Tween). - Genre-specific UI like card hands →
card-game/visual-novel. - Full input rebinding →
input-systems.
Prerequisites
- Godot 4.3 or newer project opened in the editor or available on CLI.
- A
.tscnscene file to add or edit UI within. - (Optional) A
Themeresource (.tres) if you want centralized styling.
Procedure
1. Use Control nodes for UI — not Node2D
Control nodes have a rect (position + size), anchors, and participate in focus and
theming. Node2D does not. Keep all UI under a CanvasLayer or Control subtree.
extends Control
2. Anchor for responsiveness
Anchors are fractions (0–1) of the parent rect that the Control's edges stick to. Use the editor's Layout presets (Top-Left, Full Rect, Center, etc.) instead of hand-placing pixels.
extends Control
func _ready() -> void:
# Stretch this panel to fill its parent (equivalent to "Full Rect" preset).
anchors_preset = Control.PRESET_FULL_RECT
# Or set anchors manually: all four edges at the parent's far corners.
# anchor_left = 0; anchor_top = 0; anchor_right = 1; anchor_bottom = 1
3. Let Containers position children
Put children in a VBoxContainer, HBoxContainer, GridContainer,
MarginContainer, etc. The container sets their position/size; you control flow with
size_flags. Do not set child anchors inside a container — the container
overrides them.
extends VBoxContainer # children stack vertically, auto-sized
func _ready() -> void:
for child in get_children():
if child is Button:
child.pressed.connect(_on_button_pressed.bind(child.name))
# Give the first button focus so a gamepad can navigate immediately.
if get_child_count() > 0:
(get_child(0) as Control).grab_focus()
func _on_button_pressed(which: StringName) -> void:
match which:
"PlayButton": get_tree().change_scene_to_file("res://game.tscn")
"QuitButton": get_tree().quit()
4. Size flags — make one child expand to fill leftover space
# In a HBoxContainer: a label on the left, a spacer that eats remaining width.
func _ready() -> void:
$Label.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
$Spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL # grows to fill
5. Style with a Theme resource
Assign a Theme resource on a top-level Control; children inherit it. Override
per-node with add_theme_* setters only when necessary.
func _ready() -> void:
# Per-node overrides: use add_theme_* (type-specific setters).
$Title.add_theme_font_size_override("font_size", 32)
$Title.add_theme_color_override("font_color", Color.GOLD)
$Panel.add_theme_stylebox_override("panel", preload("res://ui/panel.stylebox.tres"))
6. Wire focus for keyboard/gamepad
Set a default focused control with grab_focus(). Define focus neighbors explicitly
or rely on auto-neighbor. Ensure focus_mode is not FOCUS_NONE on interactive
controls.
7. Connect signals
Wire UI events to game logic: pressed, toggled, text_submitted,
value_changed, item_selected, etc.
8. Load the reference for deep detail
When you need the anchor/offset math, every Container type, building/extending
Theme and StyleBox resources, focus neighbor wiring, or CanvasLayer for HUDs,
load references/layout-and-theming.md from this skill folder:
# Windows / PowerShell — read the reference file
Get-Content .\references\layout-and-theming.md # if present in this folder; else Godot 4 docs for Control/Theme
Load this reference when the core steps above are insufficient — e.g., you need the full list of Container types, StyleBox subclass details, or focus neighbor properties.
Pitfalls
- Mixing manual position with Containers. A child of a
Containercannot set its own position/anchors — the container owns layout. To free-place, take the node out of the container or use a plainControl/PanelContainerwrapper. - Anchors vs offsets. Anchors are fractions of the parent; offsets are pixel deltas
from the anchored point. Set anchors via presets, then nudge with offsets. Setting
only
positionwhile anchors are at 0 makes UI not scale with the window. Node2Dfor UI. Buttons/labels parented under aNode2Dwon't theme or take focus correctly. Keep UI under aCanvasLayer/Controlsubtree.- Focus lost on gamepad. If nothing is focused, directional input does nothing.
Call
grab_focus()on an initial control and ensurefocus_modeis notFOCUS_NONE. - Theme vs theme override. A
Themeresource styles a whole subtree;add_theme_*overrides one node. Overusing per-node overrides defeats centralized theming. rect_*properties are renamed. Godot 3'srect_size/rect_position/rect_min_sizeare nowsize/position/custom_minimum_sizein 4.x.mouse_filteron a full-rect Control can swallow clicks meant for nodes beneath it; setMOUSE_FILTER_IGNOREon purely decorative panels.- Never delete existing
ThemeorStyleBoxresources referenced by scenes — unlink or replace first, then remove.
Verification
Scene opens without errors — open the
.tscnin the Godot editor; the Output dock should show no parse errors.Layout adapts to window resize — run the scene and resize the window; anchored controls should stretch, container children should reflow.
# Launch the project from CLI (Windows / PowerShell) & "C:\Program Files\Godot\Godot.exe" --path . res://your_ui_scene.tscnAdjust the Godot executable path if installed elsewhere.
Focus works on keyboard/gamepad — press arrow keys / D-pad; focus should move between buttons. Verify with:
print(get_viewport().gui_get_focus_owner())This should print the currently focused Control node name, not
null.Theme applies to subtree — confirm child nodes inherit the parent
Themeresource. A quick check in_ready():print($Title.get_theme_font_size("font_size")) # should match theme valueNo
rect_*remnants — search the.tscnfor Godot 3 property names:Select-String -Path "res://your_ui_scene.tscn" -Pattern "rect_size|rect_position|rect_min_size"No matches expected in a clean 4.x scene.
Related skills
game-ui-ux— cross-engine UI/UX: responsive scaling, safe areas, focus navigation, screen flow.godot-animation— Tween-based UI transitions and juicing.godot-signals-groups— connecting UI events to game logic.input-systems— rebindable input and multi-device focus.card-game/visual-novel— UI-heavy genre templates.