User Interface: Controls, Containers & Themes
UI nodes inherit Control: they have a rectangle defined by anchors (relative to the parent, 0–1) and offsets (pixels), size flags, focus, and theme.
24.1 Control basics
UI nodes inherit Control: they have a rectangle defined by anchors (relative to the parent, 0–1) and offsets (pixels), size flags, focus, and theme.
Label, RichTextLabel Text (RichTextLabel supports BBCode)
Button, TextureButton, CheckBox, CheckButton, OptionButton, MenuButton LineEdit, TextEdit, SpinBox
HSlider, VSlider, ProgressBar, TextureProgressBar TextureRect, NinePatchRect, ColorRect, Panel
ItemList, Tree, TabContainer, ScrollContainer PopupMenu, AcceptDialog, ConfirmationDialog, FileDialog
24.2 Containers (automatic layout)
HBoxContainer / VBoxContainer Grid (columns) Panel background + child
AspectRatioContainer, FlowContainer (HFlowContainer/VFlowContainer), SplitContainer
Children of containers are positioned by the container; use size flags (SIZE_EXPAND_FILL, SIZE_SHRINK_CENTER) and custom_minimum_size to control them.
24.3 Building UI in code
extends Control
func _ready() -> void:
set_anchors_preset(Control.PRESET_FULL_RECT)
var vbox := VBoxContainer.new()
vbox.set_anchors_preset(Control.PRESET_CENTER)
add_child(vbox)
var title := Label.new()
title.text = "My Game"
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title.add_theme_font_size_override("font_size", 48)
vbox.add_child(title)
for label in ["Play", "Options", "Quit"]:
var b := Button.new()
b.text = label
b.custom_minimum_size = Vector2(200, 48)
b.pressed.connect(_on_menu_button.bind(label))
vbox.add_child(b)
vbox.get_child(1).grab_focus() # keyboard/gamepad navigation
func _on_menu_button(label: String) -> void:
match label:
"Play": get_tree().change_scene_to_file("res://scenes/level_1.tscn")
"Quit": get_tree().quit()24.4 HUD with CanvasLayer
# hud.gd — root is CanvasLayer so UI ignores the camera
extends CanvasLayer
@onready var health_bar: ProgressBar = %HealthBar
@onready var score_label: Label = %ScoreLabel
func _ready() -> void:
Events.coin_collected.connect(_on_coin_collected)
func set_health(value: int, max_value: int) -> void:
health_bar.max_value = max_value
create_tween().tween_property(health_bar, "value", value, 0.2)
func _on_coin_collected(total: int) -> void:
score_label.text = "Score: %d" % total24.5 Themes
A Theme resource defines fonts, colors, styleboxes, constants, and icons for control types. Assign it to a root Control (children inherit) or set it project-wide (*Project Settings → GUI → Theme → Custom*).
$Button.add_theme_color_override("font_color", Color.GOLD)
$Button.add_theme_stylebox_override("normal", preload("res://ui/button_normal.tres"))
$Panel.add_theme_constant_override("margin_left", 16) # on MarginContainer
$Label.theme_type_variation = &"HeaderLabel" # variation defined in the theme
var style := StyleBoxFlat.new()
style.bg_color = Color("#223")
style.set_corner_radius_all(8)
$PanelContainer.add_theme_stylebox_override("panel", style)24.6 Input & mouse filtering, localization
mouse_filter: MOUSE_FILTER_STOP (default for most controls, consumes clicks), PASS, IGNORE (set on decorative overlays so clicks reach the game).
_gui_input(event) handles input on a specific control; accept_event() consumes it.
Localization: tr("KEY_PLAY"), translation CSV/PO files, TranslationServer.set_locale("fr"); Controls auto-translate text that matches keys.