
Godot Ui Containers
- 247 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Use godot-ui-containers for development tasks
About
godot-ui-containers: A skill for development. This provides functionality for development workflows.
- godot-ui-containers
Godot Ui Containers by the numbers
- 247 all-time installs (skills.sh)
- +26 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #1,558 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-ui-containersAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 247 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Use godot-ui-containers for development tasks
Files
UI Containers
Container auto-layout, size flags, anchors, and split ratios define responsive UI systems.
Available Scripts
responsive_layout_builder.gd
Expert container builder with breakpoint-based responsive layouts.
responsive_grid.gd
Auto-adjusting GridContainer that changes column count based on available width.
responsive_inventory_grid.gd
Expert logic for dynamic Grid columns based on available width and item minimum size.
terminal_autoscroll.gd
Safe ScrollContainer management. Handles the common "one-frame delay" bug when adding logs or chat.
viewport_3d_preview.gd
High-performance 3D-in-UI setup. Uses stretch_shrink and transparent_bg for character previews.
dynamic_tab_manager.gd
Pattern for dynamic tab spawning, custom titles, and tab closing logic.
responsive_tag_cloud.gd
Wrapping item lists using HFlowContainer, essential for tag clouds and responsive menus.
performance_anchor_layout.gd
Optimization architecture. Replaces deep container nesting with lightweight Anchor and Offset logic.
custom_radial_container.gd
Expert custom container logic implementing a radial/circle layout via NOTIFICATION_SORT_CHILDREN.
animated_container_shuffle.gd
Dynamic sibling reordering and animation logic for interactive UI lists.
aspect_ratio_mini_map.gd
Enforcing strict aspect ratios (e.g. 1:1, 16:9) across fluid window resizes using AspectRatioContainer.
container_size_flags_pro.gd
Advanced sizing logic using SIZE_EXPAND_FILL and stretch_ratio for weighted layouts.
NEVER Do in UI Containers
- NEVER ignore `mouse_filter` properties; strictly set to
PASSorIGNOREon overlay containers to prevent them from blocking clicks to underlying buttons. - NEVER instantiate thousands of nodes in a
ScrollContainer; strictly use Virtual List Pooling with aVScrollBarhook and a single spacer child to simulate list height for O(1) rendering performance. - NEVER manually calculate card dimensions for responsive grids; strictly use an `AspectRatioContainer` to lock proportions (e.g., 2:3 ratio) while allowing parent containers to handle scaling.
- NEVER manually set child `position` or `size` in a Container — Containers override child transforms during
queue_sort(). Usecustom_minimum_sizeorsize_flagsinstead [1]. - NEVER forget `size_flags` for expansion — Default is
SIZE_SHRINK_BEGIN. Children will stay tiny unless you setSIZE_EXPAND_FILLfor responsive containers. - NEVER use `GridContainer` without setting `columns` — Default is 1, creating a simple vertical list. For responsive wrapping, use
HFlowContainerinstead [8]. - NEVER nest containers too deeply (10+ levels) — Heavy nesting causes layout recalculation spikes. Replace intermediate containers with Anchor Layouts for static padding [16].
- NEVER skip separation overrides — Default theme separation is often too tight. Use
add_theme_constant_override("separation", value)for professional breathing room. - NEVER use `ScrollContainer` without a minimum size — Without it, the container may collapse to zero or expand infinitely, breaking the scroll mechanism.
- NEVER scroll to a new child on the same frame it was added — The layout hasn't updated yet. You MUST
await get_tree().process_framebefore settingscroll_vertical[5]. - NEVER scale a `SubViewportContainer` to change its size — This distorts the rendered contents. Adjust margins or use
stretchandstretch_shrinkproperties instead [2]. - NEVER leave `mouse_filter` on default for layered Viewports — Input events might not reach children. Use
MOUSE_FILTER_PASSorSTOPto ensure events drill down [6]. - NEVER use `GridContainer` for responsive wrapping — Use
HFlowContainerif you want items to wrap based on width. GridContainer enforces a strict column count [7]. - NEVER animate `position` directly inside a container — Use
Tweenoncustom_minimum_sizeto smoothly "push" siblings during transitions [1].
---
# VBoxContainer example
# Automatically stacks children vertically
# Children:
# Button ("Play")
# Button ("Settings")
# Button ("Quit")
# Set separation between items
$VBoxContainer.add_theme_constant_override("separation", 10)Responsive Layout
# Use anchors and size flags
func _ready() -> void:
# Expand to fill parent
$MarginContainer.set_anchors_preset(Control.PRESET_FULL_RECT)
# Add margins
$MarginContainer.add_theme_constant_override("margin_left", 20)
$MarginContainer.add_theme_constant_override("margin_right", 20)Expert Layout Patterns
1. Split-Screen-Container (Dynamic)
Standard pattern for local multiplayer or comparisons using HSplitContainer.
# split_screen.gd
func setup_split(v1: SubViewport, v2: SubViewport):
var hsplit = HSplitContainer.new()
var c1 = SubViewportContainer.new()
c1.stretch = true # Resize viewport to match container
c1.add_child(v1)
hsplit.add_child(c1)
# repeat for c2/v2...2. Virtual List ScrollContainer (Pooling)
High-performance list for thousands of items by recycling a small node pool and using a spacer.
# virtual_list.gd
func _on_scroll():
var scroll_y = get_v_scroll_bar().value
var start_idx = int(scroll_y / item_height)
for i in range(node_pool.size()):
var node = node_pool[i]
# Move node down the list
node.position.y = (start_idx + i) * item_height
# Inject data from the massive array
node.update_data(massive_data_array[start_idx + i])3. Aspect-Ratio-Locked Cards
Responsive cards that maintain proportions (e.g., 2:3) in any grid or flow container.
# card_grid.gd
func add_card(texture: Texture2D):
var arc = AspectRatioContainer.new()
arc.ratio = 0.66 # 2:3 proportions
arc.stretch_mode = AspectRatioContainer.STRETCH_FIT
var tr = TextureRect.new()
tr.texture = texture
tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
arc.add_child(tr)
grid_container.add_child(arc)SizeFlags
# Control how children expand in containers
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
button.size_flags_vertical = Control.SIZE_SHRINK_CENTERReference
Related
- Master Skill: godot-master
# animated_container_shuffle.gd
# Shuffling items inside a container via code-driven sibling reordering
extends VBoxContainer
func shuffle_children() -> void:
var children = get_children()
children.shuffle()
for i in range(children.size()):
# move_child triggers the Container's sort notification
move_child(children[i], i)
# Optional: Trigger a slight scale bounce for feedback
for child in get_children():
var tween = create_tween()
tween.tween_property(child, "scale", Vector2(1.1, 1.1), 0.1)
tween.tween_property(child, "scale", Vector2.ONE, 0.1)
# aspect_ratio_mini_map.gd
# Enforcing aspect ratios for UI elements across window resizes [12]
extends AspectRatioContainer
func _ready() -> void:
# Forces a 1:1 square for a mini-map
ratio = 1.0
# STRETCH_FIT: Scales the child as large as possible without clipping
stretch_mode = AspectRatioContainer.STRETCH_FIT
alignment_horizontal = AspectRatioContainer.ALIGNMENT_CENTER
alignment_vertical = AspectRatioContainer.ALIGNMENT_CENTER
# container_size_flags_pro.gd
# Expertly managing flexible sizing using Stretch Ratios [17]
extends HBoxContainer
func add_weighted_panels() -> void:
var sidebar := Panel.new()
var main_content := Panel.new()
# Sidebar: fixed minimum width, no expansion
sidebar.custom_minimum_size.x = 200
sidebar.size_flags_horizontal = Control.SIZE_FILL
# Main Content: Expands to fill, taking 4x the remaining space
main_content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
main_content.size_flags_stretch_ratio = 4.0
add_child(sidebar)
add_child(main_content)
# custom_radial_container.gd
# Custom Container logic implementing a radial/circle layout [18]
extends Container
@export var radius: float = 120.0
func _notification(what: int) -> void:
# Intercept the layout sort signal
if what == NOTIFICATION_SORT_CHILDREN:
_do_radial_sort()
func _do_radial_sort() -> void:
var children := get_children()
if children.is_empty(): return
var center := size / 2.0
var step := TAU / children.size()
for i in range(children.size()):
var child = children[i] as Control
if not child or not child.visible: continue
# Calculate polar position
var angle = i * step
var pos = center + Vector2(cos(angle), sin(angle)) * radius
# Center child on point
var min_size = child.get_combined_minimum_size()
var rect = Rect2(pos - (min_size / 2.0), min_size)
# CRITICAL: Always use fit_child_in_rect to enforce layout [18]
fit_child_in_rect(child, rect)
# dynamic_tab_manager.gd
# Expert management of TabContainer with runtime spawning and closing [13]
extends TabContainer
func add_session_tab(content: Control, title: String, icon: Texture2D) -> void:
add_child(content)
var idx := get_tab_idx_from_control(content)
# Override default node-name titles with semantic names
set_tab_title(idx, title)
set_tab_icon(idx, icon)
# Focus the new tab
current_tab = idx
func close_focused_tab() -> void:
var control := get_current_tab_control()
if control:
# TabContainer automatically removes the tab when the child is freed
control.queue_free()
# performance_anchor_layout.gd
# Optimization: Replacing heavy nested containers with Anchor Layouts [16]
extends Control
# EXPERT NOTE: 10 levels of nested Containers (Margin > VBox > HBox)
# cause massive recount/layout spikes. Use Anchors for static padding.
func setup_responsive_padding(padding: float = 20.0) -> void:
# Full rect anchor
set_anchors_preset(Control.PRESET_FULL_RECT)
# Manual offsets act as responsive margins without the overhead
# of a MarginContainer node.
offset_left = padding
offset_top = padding
offset_right = -padding
offset_bottom = -padding
# skills/ui-containers/scripts/responsive_grid.gd
extends GridContainer
## Responsive Grid Expert Pattern
## Automatically adjusts columns based on container width and item min_size.
class_name ResponsiveGrid
@export var min_item_width := 100.0
@export var max_columns := 10
@export var keep_square_ratio := false
func _ready() -> void:
resized.connect(_on_resized)
sort_children.connect(_on_sort_children)
_recalculate_layout()
func _on_resized() -> void:
_recalculate_layout()
func _on_sort_children() -> void:
_recalculate_layout()
func _recalculate_layout() -> void:
if size.x == 0:
return
var available_width := size.x
var h_sep := get_theme_constant("h_separation")
# Calculate how many items fit
var tentative_cols := floori((available_width + h_sep) / (min_item_width + h_sep))
tentative_cols = clampi(tentative_cols, 1, max_columns)
if columns != tentative_cols:
columns = tentative_cols
# Optional: Enforce square aspect ratio on children if they expand
if keep_square_ratio:
_enforce_square_ratio()
func _enforce_square_ratio() -> void:
var item_width := (size.x - (columns - 1) * get_theme_constant("h_separation")) / columns
for child in get_children():
if child is Control and not child.is_queued_for_deletion():
child.custom_minimum_size.y = item_width
## EXPERT USAGE:
## 1. Attach to a GridContainer
## 2. Set Min Item Width (e.g., 150px)
## 3. Set Children Size Flags to Expand (Horizontal)
## Result: Grid flows from 1 to N columns automatically as window resizes.
# responsive_inventory_grid.gd
# Auto-adjusting GridContainer columns based on available width [10]
extends GridContainer
@export var item_min_width: float = 64.0
func _ready() -> void:
# Recalculate layout whenever the window or container resizes
resized.connect(_on_resized)
func _on_resized() -> void:
var h_sep := get_theme_constant("h_separation")
var available_width := size.x
# Compute max columns that fit without manual overflow
var max_cols := maxi(1, int((available_width + h_sep) / (item_min_width + h_sep)))
columns = max_cols
# skills/ui-containers/code/responsive_layout_builder.gd
extends Control
## UI Containers Expert Pattern
## Implements Dynamic SizeFlag Mastery and Programmatic Grid Building.
@onready var grid_container: GridContainer = $GridContainer
# 1. Programmatic Grid Building
func populate_shop_inventory(items: Array[Dictionary]) -> void:
# Professional protocol: Clear existing children safely.
for child in grid_container.get_children():
child.queue_free()
for item in items:
var panel = _create_item_panel(item)
grid_container.add_child(panel)
func _create_item_panel(data: Dictionary) -> PanelContainer:
var panel = PanelContainer.new()
# 2. Dynamic SizeFlag Mastery
# Expert logic: Force the panel to fill available space in the grid.
panel.size_flags_horizontal = SIZE_EXPAND_FILL
panel.size_flags_vertical = SIZE_SHRINK_CENTER
var label = Label.new()
label.text = data.get("name", "Unknown Item")
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
panel.add_child(label)
return panel
# 3. Layout-Recalculation Hooks
func force_refresh_layout() -> void:
# Professional protocol: Explicitly trigger the container's sort
# if content changes size at runtime (e.g. after a text swap).
var parent = grid_container.get_parent()
if parent is Container:
# Containers automatically sort, but some custom implementations
# need a manual nudge or frame wait.
grid_container.queue_sort()
## EXPERT NOTE:
## Use 'Container-Safe Nesting': Wrap every GridContainer in a
## 'MarginContainer' to ensure consistent padding without
## calculating 'position' offsets manually.
## For 'ui-containers', use 'Aspect Ratio Scaling': Wrap important UI
## elements in an 'AspectRatioContainer' (set to 16:9 or 1:1) to
## prevent distortion on ultra-wide or vertical mobile displays.
## NEVER use 'position' or 'size' properties directly on children
## of a Container; always use 'custom_minimum_size' and 'size_flags'.
## Manual offsets are the #1 cause of broken UI on different resolutions.
# responsive_tag_cloud.gd
# Wrapping item lists using HFlowContainer [8, 15]
extends HFlowContainer
func populate_tags(tags: Array[String]) -> void:
# Clear existing
for child in get_children(): child.queue_free()
# HFlowContainer automatically wraps items to next line
# based on available width.
last_wrap_alignment = FlowContainer.LAST_WRAP_ALIGNMENT_BEGIN
for tag_text in tags:
var lbl = Label.new()
lbl.text = "#" + tag_text
add_child(lbl)
# terminal_autoscroll.gd
# Safe ScrollContainer management for logs/chat [4, 5]
extends ScrollContainer
@onready var content_vbox: VBoxContainer = $VBoxContainer
func append_log(log_node: Control) -> void:
var at_bottom := _is_at_bottom()
content_vbox.add_child(log_node)
# ANTI-PATTERN PREVENTION: The scrollbar doesn't update until the
# layout is recalculated. You MUST wait a frame [5].
await get_tree().process_frame
if at_bottom:
scroll_vertical = int(get_v_scroll_bar().max_value)
func _is_at_bottom() -> bool:
var bar := get_v_scroll_bar()
# Check if the current scroll is near the bottom (max - page)
return scroll_vertical >= (bar.max_value - bar.page - 10)
# viewport_3d_preview.gd
# Responsive 3D-in-UI setup using SubViewportContainer [3, 11]
extends SubViewportContainer
@onready var viewport: SubViewport = $SubViewport
func _ready() -> void:
# stretch = true: The internal viewport size follows the UI container [3]
stretch = true
# stretch_shrink: Renders at half resolution for performance while
# maintaining UI crispness.
stretch_shrink = 2
# Ensure background transparency for HUD overlays
viewport.transparent_bg = true