Skip to content
Development
Skill

/godot-ui-control

Build Godot 4.7 user interfaces with Control nodes: anchors and offsets for responsive layout, Container nodes (VBox/HBox/Grid/Margin) for automatic arrangement, Theme resources for consistent styling, and keyboard/gamepad focus navigation. Use when laying out a HUD, menu, or UI

From plugin
awesome-gamedev-agent-skills
6.3k77 skills

Claude Code

$ npx -y skills add gamedev-skills/awesome-gamedev-agent-skills --skill godot-ui-control --agent claude-code

SKILL.md

godot-ui-control.SKILL.md
name: godot-ui-control
description: >
  Godot 4.7 Control-node API for menus and HUDs: anchors, Containers, Theme and StyleBox, focus
  neighbours and stretch settings, with a headless layout check. Use when Godot UI overlaps or
  drifts on resize, buttons ignore the gamepad, or an invisible panel swallows clicks. Not for
  engine-neutral UI rules (`game-ui-ux`; load both) or rebinding (`input-systems`).

Godot UI / Control nodes (4.x)

Lay out responsive UI with `Control` anchors and `Container` nodes, style it with a `Theme`, make it navigable by keyboard and gamepad, and prove the layout holds at more than one screen size. Targets **Godot 4.7** (see Version notes). Design rules (what a good HUD or menu needs) live in `game-ui-ux`; load both for engine UI work.

When to use

  • HUDs, menus, inventories, dialog boxes or settings screens built from `Control` nodes;

UI that must adapt to window size; theming; focus navigation for controller/keyboard.

**When *not* to use:** in-world `Node2D`/sprites → `godot-nodes-scenes`; UI tweens → `godot-animation`; rebinding → `input-systems`; card hands and VN text boxes → `card-game` / `visual-novel`.

Core workflow

1. **Read the stretch settings first.** `project.godot` `[display]` holds `window/size/viewport_width`/`viewport_height` (base size, default 1152x648), `window/stretch/mode` (`canvas_items` is the usual choice for UI-heavy games) and `window/stretch/aspect` (`expand` lets UI use extra width or height). Layout bugs on other screens usually start here. 2. **Use `Control` nodes for UI**, not `Node2D`, and put HUDs under a `CanvasLayer` so the game camera does not move them. 3. **Anchor top-level panels; let Containers place their children.** Outside containers, set anchors with presets (`set_anchors_and_offsets_preset()`). Inside a `VBoxContainer`, `HBoxContainer`, `GridContainer` or `MarginContainer`, the container owns position and size; steer it with `size_flags_*`. In a `.tscn`, a Full Rect panel reads:

   [node name="PauseMenu" type="Control"]
   anchors_preset = 15
   anchor_right = 1.0
   anchor_bottom = 1.0
   grow_horizontal = 2
   grow_vertical = 2

Keep whatever `layout_mode` line Godot wrote; it records whether anchors or a parent container drive the node. 4. **Style with one `Theme`.** Set a project-wide theme with `gui/theme/custom="res://ui/theme.tres"` in `project.godot`, or assign `theme` on the top Control; children inherit it. Use per-node `add_theme_*_override` only for exceptions. 5. **Wire focus.** Give the first button focus when a menu opens (`grab_focus()`), keep `focus_mode = FOCUS_ALL` on anything a gamepad must reach, and set `focus_neighbor_*` only where the automatic geometry guess is wrong. 6. **Connect signals** (`pressed`, `toggled`, `text_submitted`, `value_changed`). 7. **Check before you finish.**

  • Run the layout test (Pattern 4) headless:

`godot --headless --path . --script res://tests/test_ui_layout.gd` and read the exit code. It flags controls pushed off-screen and menus with nothing focused, at each size.

  • Run the `godot-nodes-scenes` skill's `scripts/check_godot_project.py <project-dir>` if you edited

`.tscn`/`.tres` files (themes, StyleBoxes, fonts).

  • For a picture, on a machine with a GPU (not `--headless`), capture frames and look

at them: `godot --path . --resolution 2560x1080 --write-movie $OUT/menu.png --quit-after 3 --scene res://ui/main_menu.tscn` writes numbered PNGs next to that path. Repeat at 1280x720.

  • With no Godot binary (`godot`, `godot4` or `$GODOT_BIN`), ask the user to run these

and paste the output or screenshots. Ask the user to try the menu with a gamepad and report any button that cannot be reached.

Patterns

1. Full Rect from code (anchors *and* offsets)

extends Control

func _ready() -> void:
    # Setting only anchors (or `anchors_preset`) keeps the old offsets, so the rect
    # would not snap to the preset. This moves anchors AND resets offsets.
    set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

2. A menu from containers, with initial focus

extends VBoxContainer    # children stack vertically, auto-sized

func _ready() -> void:
    for child in get_children():
        if child is Button:
            child.pressed.connect(_on_button_pressed.bind(child.name))
    # Give the first button focus so a gamepad can navigate immediately.
    if get_child_count() > 0:
        (get_child(0) as Control).grab_focus()

func _on_button_pressed(which: StringName) -> void:
    match which:
        "PlayButton":  get_tree().change_scene_to_file("res://game.tscn")
        "QuitButton":  get_tree().quit()

3. One child takes the leftover space

func _ready() -> void:
    $Label.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
    $Spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL   # grows to fill

4. Headless layout test at several sizes

# res://tests/test_ui_layout.gd
extends SceneTree

# The sizes the UI really gets. With stretch mode canvas_items and aspect "expand",
# a 2560x1080 window on a 1152x648 base gives the UI 1536x648, not 2560x1080.
const SIZES: Array[Vector2i] = [Vector2i(1152, 648), Vector2i(1536, 648), Vector2i(1152, 864)]

func _initialize() -> void:
    var failed := 0
    for size in SIZES:
        var vp := SubViewport.new()
        vp.size = size
        root.add_child(vp)
        var ui: Control = load("res://ui/main_menu.tscn").instantiate()
        vp.add_child(ui)
        await process_frame
        await process_frame                      # containers re-sort a frame later
        var screen := Rect2(Vector2.ZERO, Vector2(size))
        for node in ui.find_children("*", "Control", true, false):
            var c := node as Control
            if c.is_visible_in_tree() and not screen.encloses(c.get_global_rect()):
                failed +=
Read more
@gamedev-skills
Ships withawesome-gamedev-agent-skills

<img src="docs/assets/awesome-gamedev-agent-skills-banner.png" width="820" alt="awesome-gamedev-agent-skills — game-dev skills for AI coding agents.

Stars on GitHub

Momentum

stars in total
6.3k
Star history of gamedev-skills/awesome-gamedev-agent-skills on GitHub, a chart by star-history.com

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills12d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc13d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react3d252k
@facebook@facebookInstall Skill

Questions about godot-ui-control.

Which coding agents can use godot-ui-control?
godot-ui-control installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.