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" % total

24.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.