UI — Control Nodes and Containers
Control, anchors, containers, Theme and StyleBox.
In Godot, UI consists of Control nodes. This is a separate hierarchy of CanvasItem descendants,
with its own layout engine, anchors, and events. Unlike Unity’s uGUI / UI Toolkit, here there is a
single, built-in UI system.
Control Hierarchy
CanvasItem
├── Node2D (for 2D gameplay)
└── Control (for UI)
├── Container (HBox/VBox/Grid/...)
├── Button / Label / LineEdit / ...
├── Panel / PanelContainer
└── ColorRect / TextureRect
All Control nodes have:
- position + size — position and size in pixels.
- anchors + offsets — anchoring to the parent (like Unity uGUI anchors or CSS absolute).
- size_flags_horizontal / size_flags_vertical — behavior inside a container (
FILL,EXPAND,SHRINK_CENTER, …). - theme + theme_overrides — styling.
Anchors — the Essentials
An anchor is a point on the parent to which a side of the current Control is “glued.” Four floats (top, left, bottom, right) in the range 0..1. Combinations produce different behaviors:
(0, 0, 0, 0)— everything in the top-left corner of the parent. Fixed size.(0.5, 0.5, 0.5, 0.5)— center. Fixed size.(0, 0, 1, 1)— stretches across the entire parent.(0, 0, 1, 0)— stretches horizontally, pinned to the top. Fixed height.
For convenience, the editor has Anchor presets — a button in the Control’s top toolbar: “Top Left”, “Top Right”, “Center”, “Full Rect”, and so on.
CSS position: absolute; top/left/right/bottom + flexbox. Anchors are the absolute attachment
points; offsets are the top/left/... values from them.
The concept is the same as Unity’s uGUI RectTransform. In Godot it is built into the common
Control; there is no separate RectTransform.
Containers — Auto-Layout
Container nodes automatically lay out their children:
- HBoxContainer / VBoxContainer — horizontal / vertical flexbox.
- GridContainer — N columns.
- MarginContainer — padding.
- CenterContainer — centers its contents.
- AspectRatioContainer — maintains an aspect ratio.
- PanelContainer — a wrapper with a background (StyleBox).
- ScrollContainer — scrolls its contents.
- TabContainer / HSplitContainer / VSplitContainer / HFlowContainer / VFlowContainer — niche.
The behavior of a container’s children is determined by their size_flags_horizontal /
size_flags_vertical:
FILL— fills the available space.EXPAND— also takes any extra space.SHRINK_CENTER / BEGIN / END— alignment without stretching.
VBoxContainer
├── Label "Score"
├── HBoxContainer
│ ├── Button "Pause" (size_flags = EXPAND_FILL)
│ └── Button "Quit" (size_flags = EXPAND_FILL)
└── PanelContainer (size_flags = EXPAND_FILL)
└── Label "Status..."
Do not position UI manually via position. Use Containers — the layout is computed automatically
at any resolution. Leave manual positioning for overlay effects (drag preview, custom drawing).
Basic Controls
- Label — text.
text,horizontal_alignment,vertical_alignment,autowrap_mode. - RichTextLabel — text with BBCode (colors, links, images, custom effects).
- Button — a button. The
pressedsignal. - TextureButton — an image button with normal/pressed/hover textures.
- LineEdit / TextEdit — single-line / multi-line input.
- CheckBox / CheckButton / OptionButton (combo box).
- Slider (
HSlider/VSlider), SpinBox, ProgressBar. - TextureRect / ColorRect — a static image / a colored rectangle.
- NinePatchRect — a stretchable texture with fixed corners (for UI panels).
UI Signals
The main ones:
- Button.pressed — a click.
- Button.toggled(button_pressed) — for toggle buttons.
- LineEdit.text_changed(new_text) / text_submitted(text) (Enter).
- Slider.value_changed(value).
- Control.mouse_entered / mouse_exited.
- Control.focus_entered / focus_exited.
They are connected via Inspector → Node → Signals or from code:
@onready var start_button: Button = $StartButton
func _ready() -> void:
start_button.pressed.connect(_on_start_pressed)
func _on_start_pressed() -> void:
get_tree().change_scene_to_file("res://levels/level_01.tscn")
Theme and StyleBox
Theme is a resource containing all styles. It is applied to the root Control (or to the whole
project via Project Settings → GUI → Theme → Custom).
A Theme describes:
- Colors — colors for each state of each type (Button.font_color, Button.font_color_hover).
- Fonts — fonts for each type.
- Constants — numbers (margins, padding).
- Icons —
Texture2Dfor icons. - StyleBoxes — backgrounds and borders.
StyleBox — background and outline. Types:
- StyleBoxFlat — a fill with corner radius, outline, and shadow. The main one for modern UIs.
- StyleBoxTexture — a stretchable texture (like NinePatchRect, but in a Theme).
- StyleBoxLine — a line (separators).
- StyleBoxEmpty — invisible.
var box = StyleBoxFlat.new()
box.bg_color = Color("2a2a2a")
box.border_color = Color("6cb6ff")
box.border_width_left = 2
box.border_width_top = 2
box.border_width_right = 2
box.border_width_bottom = 2
box.corner_radius_top_left = 8
box.corner_radius_top_right = 8
box.corner_radius_bottom_left = 8
box.corner_radius_bottom_right = 8
var theme = Theme.new()
theme.set_stylebox("normal", "Button", box)
You can also override styles directly on a specific node via theme_override_styles (the “Theme Overrides” section in the Inspector).
CanvasLayer — UI on Top of 3D
So that UI is drawn on top of 3D and does not follow the camera, wrap it in a CanvasLayer:
GameScene (Node3D)
├── Player
├── World
└── HUD (CanvasLayer)
└── Control
├── HealthBar
└── ScoreLabel
Without a CanvasLayer, Control nodes end up in the default canvas and may be overlapped by 3D objects depending on the Z-order.
CanvasLayer.layer — a numeric order (0 = default, higher = on top).
UI in the 3D World — SubViewport
For “in-world” UI (labels above enemies, screens in a room), a SubViewport renders the UI into a
texture that you can apply to a MeshInstance3D:
EnemyNameTag (Node3D)
├── SubViewport
│ └── Label "Enemy: Goblin"
└── MeshInstance3D (Quad)
└── material_override.albedo_texture = ViewportTexture(SubViewport)
This is an analog of Unity’s World Space Canvas, via the ViewportTexture mechanism.
The next chapter covers PackedScene and Resource.