~3 min read

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.

Web

CSS position: absolute; top/left/right/bottom + flexbox. Anchors are the absolute attachment points; offsets are the top/left/... values from them.

Unity

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..."
Containers are all you need

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 pressed signal.
  • 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).
  • IconsTexture2D for 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.