Godot UI — Control Nodes, Themes & Layout
All examples target Godot 4.3+ with no deprecated APIs; GDScript first, then C#.
Related skills: responsive-ui for multi-resolution scaling, hud-system for in-game HUD patterns, dialogue-system for dialogue UI presentation, tween-animation for UI transition and animation effects.
1. Control Node Hierarchy
How Control Differs from Node2D
Control is the base class for all UI nodes — it lives in a separate scene-tree branch from Node2D/Node3D with a fundamentally different layout model.
| Feature | Node2D |
Control |
|---|---|---|
| Position model | World-space position (pixels from parent) |
Anchor + offset relative to parent rect |
| Size | No intrinsic size | Has size, minimum_size, custom_minimum_size |
| Theme | None | Inherits and overrides Theme resources |
| Focus | Not applicable | Built-in focus system (focus_mode, grab_focus()) |
| Mouse events | Manual via _input |
gui_input, mouse_entered, mouse_exited |
| Layout helpers | None | Container subclasses auto-arrange children |
Control as Base Class
Every UI widget (Button, Label, LineEdit, etc.) extends Control. Key properties defined on Control itself:
anchor_left,anchor_top,anchor_right,anchor_bottom— fractional values (0.0–1.0) relative to the parent rectoffset_left,offset_top,offset_right,offset_bottom— pixel offsets applied after the anchor resolvessize_flags_horizontal,size_flags_vertical— how the node participates inContainerlayouttheme— aThemeresource; ifnull, walks up the tree to the nearest ancestor with onefocus_mode— whether the node can receive keyboard/gamepad focus
Place UI nodes inside a CanvasLayer (or directly under the scene root's built-in canvas) so they render on top of the 3D/2D world, unaffected by Camera transforms.
⚠️ Changed in Godot 4.7:
Control.accessibility_livechanged type fromDisplayServer.AccessibilityLiveModetoAccessibilityServer.AccessibilityLiveMode(LIVE_OFF = 0default,LIVE_POLITE,LIVE_ASSERTIVE) — accessibility enums/APIs moved to the newAccessibilityServersingleton. GDScript-compatible; breaks C# binary/source compatibility (rebuild against the new enum). See the 4.7 migration guide.
2. Common Container Nodes
| Container | Purpose | When to Use |
|---|---|---|
VBoxContainer |
Stacks children vertically | Lists, option rows, vertical menus |
HBoxContainer |
Stacks children horizontally | Toolbars, stat rows, horizontal nav |
GridContainer |
Arranges children in a fixed-column grid | Inventory grids, key-binding tables |
MarginContainer |
Adds padding around a single child | Wrapping any node to give it breathing room |
PanelContainer |
Draws a StyleBox background, then lays out children |
Card UI, dialog boxes, HUD panels |
ScrollContainer |
Makes its single child scrollable; clips overflow | Long lists, logs, scrollable settings |
TabContainer |
Stacks children as named tabs; shows one at a time | Settings screens, multi-section panels |
Sizing tips:
- Set
size_flags_horizontal = SIZE_EXPAND_FILLon children that should fill available space. - Use
custom_minimum_sizeto prevent a child from collapsing to zero. MarginContainerreads margin from the theme propertymargin_*; override at runtime withadd_theme_constant_override("margin_left", 16).
Godot 4.7+:
custom_maximum_size(Vector2(-1, -1)) caps size per axis, prioritized overcustom_minimum_size;propagate_maximum_size(defaultfalse) makes a parent's maximum constrain its Control children;_get_maximum_size()computes maximums from code.
⚠️ Changed in Godot 4.7:
TabContainer.all_tabs_in_frontis deprecated — it does nothing now, since tabs are always in front. Remove code that sets it. See GH-118623.
3. Anchors & Margins
How Anchor Presets Work
An anchor is a point on the parent rect expressed as a fraction (0 = top/left edge, 1 = bottom/right edge). Godot resolves the final pixel position of each edge as:
final_left = parent_width * anchor_left + offset_left
final_top = parent_height * anchor_top + offset_top
final_right = parent_width * anchor_right + offset_right
final_bottom = parent_height * anchor_bottom + offset_bottom
The editor exposes built-in presets:
| Preset | Anchor values | Use case |
|---|---|---|
| Full Rect | L=0, T=0, R=1, B=1 | Overlay / fill parent — most common for root UI |
| Center | L=0.5, T=0.5, R=0.5, B=0.5 | Fixed-size widget centred in parent |
| Top Left | L=0, T=0, R=0, B=0 | Fixed-size widget pinned to top-left corner |
| Top Right | L=1, T=0, R=1, B=0 | Fixed-size widget pinned to top-right corner |
| Bottom Center | L=0.5, T=1, R=0.5, B=1 | HUD element anchored to bottom centre |
Setting Anchors in Code
Anchors resolve as parent_size * anchor + offset per edge, so setting them by hand means setting eight properties. set_anchors_and_offsets_preset(Control.PRESET_*) does it in one call — use that, then adjust offset_* for margins (negative on right/bottom).
The anchor-vs-offset rule (keep offsets at 0 and let anchors do the work) plus full GDScript + C# examples — full-rect fill, top-right HUD with 16 px margins, and a custom half-screen side panel: references/anchors-in-code.md
4. Theme System
A Theme resource centralizes fonts, colors, and StyleBoxes. Apply at the root and let inheritance do the work; use theme_override_* only for one-off tweaks. StyleBoxFlat covers most flat-design needs (bg_color, border_color, corner_radius, border_width); StyleBoxTexture for textures.
See references/theme-system.md for the full Theme resource creation walk-through, StyleBoxFlat properties, font overrides, theme inheritance rules, and per-node
theme_override_*methods.
Godot 4.7+:
GradientTexture2D'sFillenum gainsFILL_CONIC— colors interpolated in a cone (angular) pattern; radial progress/cooldown indicators without a shader (C#:FillEnum.Conic).
5. Focus & Navigation
Focus modes (FOCUS_NONE, FOCUS_CLICK, FOCUS_ALL) gate keyboard/gamepad navigation. Wire chains with focus_neighbor_top / _bottom / _left / _right, or rely on automatic spatial detection. Call grab_focus() on the first interactive element when a menu opens.
See references/focus-and-navigation.md for focus mode details,
focus_neighborchain examples, gamepad/keyboard input handling, and grab_focus patterns.
6. Common UI Patterns
Three canonical scenes: a main menu (centered VBoxContainer with title + button list), a settings screen with tabs (TabContainer + child panels per category), and a pause menu overlay (full-rect ColorRect background + centered options panel, paused via get_tree().paused = true).
See references/ui-patterns.md for the full scene-tree fragments and GDScript wiring for each pattern.
Godot 4.7+:
offset_transform_*— visual-only UI-juice transform (shake/pulse) that never re-triggers container layout;_get_cursor_shape(at_position)— per-position cursor shapes;PopupMenusearch bar (search_bar_enabled, fuzzy by default) plusset_item_index()for reordering;TextureRectSTRETCH_TILEnow tilesAtlasTextures (only non-zeromarginunsupported). Code: references/ui-patterns.md.
⚠️ Changed in Godot 4.7:
RichTextLabel.add_image()/update_image()sizing was reworked —width/heightare nowfloat;width_in_percent/height_in_percentbools becomewidth_unit/height_unit, taking the newImageUnitenum (IMAGE_UNIT_PIXEL,IMAGE_UNIT_PERCENT,IMAGE_UNIT_EM— em scales with font size).ImageUpdateMask.UPDATE_WIDTH_IN_PERCENTis renamedUPDATE_WIDTH_UNIT, breaking GDScript using the old name. See the 4.7 migration guide.
7. Signals
Button.pressed for clicks, Control.gui_input for raw events on a node, Control.mouse_entered / mouse_exited for hover. Connect in _ready() or via the Inspector's Node panel.
See references/signals.md for the complete signal catalog and signal-driven UI update patterns.
8. FoldableContainer (Godot 4.5+)
FoldableContainer is a built-in accordion Container added in Godot 4.5 — a toggle header plus collapsible children, replacing the old boilerplate of wiring a Button to show/hide a VBoxContainer. Set title, set folded for the initial state, add children normally, and listen to folding_changed(is_folded).
Full GDScript + C# construction, the key-properties table, and the toggle signal: references/foldable-container.md
9. Stacked Label Effects (Godot 4.5+)
Godot 4.5 lets Label and RichTextLabel layer multiple text effects simultaneously — e.g., stacking two outline effects at different widths/colors, or combining a shadow with a glow. Previously, multiple outline layers required duplicating and manually layering Label nodes.
# Configure via Theme Overrides → Constants in the inspector, or add_theme_* overrides at runtime.
func apply_stacked_outlines(label: Label) -> void:
# Outer outline — wide, dark
label.add_theme_constant_override("outline_size", 6)
label.add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 0.9))
# Shadow (second layered effect)
label.add_theme_constant_override("shadow_offset_x", 2)
label.add_theme_constant_override("shadow_offset_y", 2)
label.add_theme_color_override("font_shadow_color", Color(0.0, 0.0, 0.0, 0.5))
public void ApplyStackedOutlines(Label label)
{
// Outer outline — wide, dark
label.AddThemeConstantOverride("outline_size", 6);
label.AddThemeColorOverride("font_outline_color", new Color(0f, 0f, 0f, 0.9f));
// Shadow (second layered effect)
label.AddThemeConstantOverride("shadow_offset_x", 2);
label.AddThemeConstantOverride("shadow_offset_y", 2);
label.AddThemeColorOverride("font_shadow_color", new Color(0f, 0f, 0f, 0.5f));
}
For RichTextLabel, stacked effects can also be applied via BBCode combined with theme overrides:
# Multiple outline-style effects via BBCode + theme
$RichTextLabel.text = "[outline size=4 color=#000000]Level Up![/outline]"
# Additional layers via theme overrides, as above.
Editor workflow: Configure stacked effects via Theme Editor → Label → Constants, or add multiple
FontFile-style outline passes in the Font resource. The runtime API (add_theme_*_override) above covers dynamic scenarios.
10. Checklist
- Root UI
Controlhas anchor preset Full Rect (or appropriate preset for the layout) - All interactive widgets (
Button,LineEdit,Slider) havefocus_mode = FOCUS_ALL - Decorative nodes (
Label,TextureRect) havefocus_mode = FOCUS_NONE - Focus neighbours wired for non-linear layouts so gamepad navigation wraps correctly
-
grab_focus()called on the first interactive widget in_ready()for each screen - Pause menu root
Controlhasprocess_mode = PROCESS_MODE_ALWAYS - One
Themeresource assigned at the screen root — not duplicated on every child -
StyleBoxFlatused instead of image assets for simple solid-colour panels -
add_theme_*_override()used for per-node overrides rather than assigning a whole newTheme - Containers (
VBoxContainer,HBoxContainer, etc.) used for layout instead of manualpositionvalues -
custom_minimum_sizeset on widgets that must not collapse to zero - Slider and volume code uses
linear_to_db/db_to_linear— not raw linear values mapped to audio bus - Signals connected in
_ready()(or via the editor); no polling of UI state in_process - Tab order in
TabContainermatches logical reading / navigation order - Accordion-style collapsible panels use
FoldableContainerinstead of manual Button + VBoxContainer wiring (Godot 4.5+) - Multiple outline/shadow layers on
Label/RichTextLabeluse stacked theme overrides instead of duplicated nodes (Godot 4.5+)