godot-ui-theming
godot ui theming
SKILL.md
Full skill instructions
UI Theming
Theme resources, StyleBox styling, font management, and override system define consistent UI visual identity.
Available Scripts
global_theme_manager.gd
Expert theme manager with dynamic switching, theme variants, and fallback handling.
ui_scale_manager.gd
Runtime theme switching and DPI/Resolution scale management.
theme_swapper.gd
Dynamic Dark/Light mode implementation using cascading theme root propagation.
danger_button_assignment.gd
Expert use of theme_type_variation for semantic UI styling without scene duplication.
dynamic_stylebox_color.gd
Safe runtime StyleBox modification. Demonstrates the critical duplicate() pattern for isolated overrides.
procedural_theme_safe.gd
Reliable theming for generated UI elements using NOTIFICATION_THEME_CHANGED.
custom_chart_drawing.gd
Pattern for reading active Theme properties (colors, fonts) in custom _draw() logic.
theme_isolation.gd
Ensuring HUD consistency by isolating nodes from parent themes and referencing Project Defaults.
pulsating_ui_theme.gd
Animating UI styles via Tweens. Targets StyleBox properties directly after duplication.
crisp_ui_scaler.gd
High-quality resolution-independent scaling using content_scale_factor to maintain font crispness.
memory_safe_custom_drawing.gd
Fixing the "disappearing stylebox" bug by caching resources at the class level for the RenderingServer.
rtl_theme_mirroring.gd
Bi-directional (RTL/LTR) UI support. Swaps theme variants dynamically based on layout direction.
NEVER Do in UI Theming
- NEVER create StyleBox in
_ready()for many nodes — InstantiatingStyleBoxFlat.new()100 times creates 100 unique objects. Use a Theme resource for shared heritage. - NEVER forget theme inheritance — Parent themes are ignored if a child has its own theme. Apply themes at the root and use
theme_type_variationfor specific overrides. - NEVER hardcode colors in StyleBox — Use
theme.get_color()to maintain a single source of truth for your palette. - NEVER use
add_theme_overridefor global styles — This is brittle. Define styles in a Theme resource for automatic propagation across the project. - NEVER modify theme resources during
_draw()OR_process()— Frequent layout recalculations will severely degrade performance. - NEVER assign
StyleBoxEmptyto focus styles without a fallback — This invisibly breaks controller/keyboard navigation [1]. Always provide a visible alternative (e.g. scale change). - NEVER use standard
set()for theme properties — Callingnode.set("font_color", red)fails. You MUST use the dedicatedadd_theme_color_override()API [3]. - NEVER use
expand_margin_*to increase clickable area — It only expands the VISUAL bounds. Usecontent_margin_*on the StyleBox or adjust the Control's size to ensure input works [5]. - NEVER define StyleBoxes as local variables inside
_draw()— They will be garbage collected before the RenderingServer can finish drawing them [7]. Store at class level. - NEVER duplicate scenes/themes just to change one color — Use
theme_type_variationto create lightweight derived styles (e.g. "DangerButton") within the same Theme [8]. - NEVER skip
corner_radius_allshortcut — It's a useful shorthand for uniform rounding inStyleBoxFlat.
- Project Settings → GUI → Theme
- Create new Theme resource
- Assign to root Control node
- All children inherit theme
Godot 4.7: UI Theming
- Control offset transform for inspector-driven visual tweaks without relayout.
ResourceImporterDynamicFont.hintingdefault changed to 3 — verify font crispness on target DPI.- GradientTexture2D supports conic gradients.
StyleBox Pattern
# Create StyleBoxFlat for buttons
var style := StyleBoxFlat.new()
style.bg_color = Color.DARK_BLUE
style.corner_radius_top_left = 5
style.corner_radius_top_right = 5
style.corner_radius_bottom_left = 5
style.corner_radius_bottom_right = 5
# Apply to button
$Button.add_theme_stylebox_override("normal", style)
Font Loading
# Load custom font
var font := load("res://fonts/my_font.ttf")
$Label.add_theme_font_override("font", font)
$Label.add_theme_font_size_override("font_size", 24)
Expert Theming Patterns
1. Shared-Color-Palette (The Static Pattern)
Maintain a single source of truth for UI colors accessible to both the Theme Editor and GDScript.
- Theme Setup: In your
.themefile, create a custom type calledPaletteand addColoritems (e.g.,primary,danger,accent). - Static Access: Use a
SharedPaletteclass withstatic func get_primary() -> Colorthat pulls fromThemeDB.get_project_theme(). This ensures UI scripts and the visual theme never drift.
2. Theme-Type-Variations
Avoid duplicating button scenes or styleboxes for variants like "Danger" or "Ghost" styles.
- Implementation: In the Theme Editor, create a new Type Variation. Set its Base Type to
Button. - Inheritance: The variation inherits all properties from the base type. You only override what's different (e.g., set
font_colorto red forDangerButton). - Usage: Assign via code
node.theme_type_variation = &"DangerButton"or via the Inspector dropdown.
3. Runtime-Theme-Swapping (Accessibility)
Efficiently switch the visual style of the entire game for Light, Dark, or High-Contrast modes.
- Cascading Updates: Assign a new
Themeresource to the root Control node. Godot propagates this to every descendant. - Accessibility: Use
NOTIFICATION_THEME_CHANGEDto update elements that don't support automatic theming (like custom_draw()logic or RichText effects). - High-Contrast: Ensure High-Contrast themes use pure black/white and thicker focus outlines for low-vision accessibility.
4. Themed-Asset-Loading (Seasonal Variants)
Godot Themes support more than just colors and fonts—they can store textures.
- Setup: Define UI icons as Icon items within separate Theme resources (e.g.,
halloween.theme,christmas.theme). - Swapping: Swapping the root theme resource instantly cascades the new icon textures across all buttons and panels without manual logic.
5. UI-Focus-Manager (Dynamic Controller Icons)
Standard focus styles are static. For professional UX, swap controller icons based on the connected device.
- Detection: Use
Input.get_joy_name(device)to identify the controller (e.g., "PS4 Controller", "Xbox One Controller"). - Implementation:
func _on_joy_connection_changed(device: int, connected: bool): if connected: var joy_name = Input.get_joy_name(device).to_lower() if "xbox" in joy_name: _set_prompt_icons("res://ui/icons/xbox/") elif "playstation" in joy_name or "ps" in joy_name: _set_prompt_icons("res://ui/icons/ps/") - Interpolation: When a node gains focus, use a
Tweento move a dedicated "Highlight Panel" to the node'sget_global_rect().
6. Asset-Dependency-Audit (Draw-Call Reduction)
Ensuring UI textures are optimized for rendering performance.
- Atlas Packing: Use
AtlasTextureto crop small UI elements from a singular large sheet. This reduces VRAM state changes and minimizes draw calls [14]. - Compression Policy:
- 2D/Pixel Art: Use Lossless compression to avoid blurry artifacts [15].
- UI Backgrounds: Use Lossy or Basis Universal for large illustrations to save disk space without decreasing VRAM usage [15].
- Audit: Use
ResourceLoader.get_dependencies(scene_path)to ensure no uncompressed raw assets (e.g..png) are leaking into the final export [19].
Reference
Related
- Master Skill: godot-master
