UMG & Slate
UMG is Unreal's widget-based UI framework: a Widget Blueprint pairs with a C++
UUserWidget subclass that holds logic, while the Blueprint handles layout and styling.
Underneath sits Slate, the lower-level C++ UI framework. Prefer UMG for game UI and
CommonUI for anything with menus or multiplatform input routing.
When to use this skill
- Creating a HUD, menu, inventory screen, or any on-screen UI.
- Wiring UMG widgets to gameplay data or handling button/input events in C++.
- Choosing how to create, show, and hide widgets at runtime.
- Building a 3D in-world widget on an actor (
UWidgetComponent). - Deciding between UMG, raw Slate, or CommonUI.
- Diagnosing or preventing UI performance problems (draw calls, per-frame cost, hitches).
- Structuring screens/menus for a real game (layer stacks, MVVM, resolution independence).
Mental model
UUserWidget— the C++ base for every Widget Blueprint. Holds logic, references to child widgets, and lifecycle callbacks.- Widget tree — a
UWidgetTree(WidgetTree.h) owns allUWidgetinstances that make up the layout of aUUserWidget. UWidget(Widget.h) — base of all UMG leaf and panel widgets; wraps a SlateSWidget.- Slate — the lower platform-level framework.
SWidget(SlateCore) →SCompoundWidget→ specialized widgets. UMG wraps Slate; you only author Slate for editor tools or bespoke widgets.
UUserWidget lifecycle
Declare in UserWidget.h:1582-1586.
| Callback | When | Put here |
|---|---|---|
NativeOnInitialized() |
Once, after the widget object is constructed and the widget tree is built — before it is ever shown. | One-time setup that does not need a world (register non-world delegates, cache sub-widget refs). |
NativePreConstruct() |
Every time the Widget Blueprint CDO is compiled or the designer refreshes. | Design-time preview work only. |
NativeConstruct() |
Widget is added to the viewport / displayed — analogous to BeginPlay. |
Bind OnClicked, start timers, fetch game state. |
NativeDestruct() |
Widget is removed from the viewport. | Clean up delegates/timers. |
NativeTick(Geometry, DeltaTime) |
Per frame — only if tick is needed. | Per-frame updates (prefer push-based updates instead). |
UUserWidget has meta=(DisableNativeTick) on its UCLASS by default; override
NativeTick only when genuinely needed or enable ticking explicitly.
The BindWidget pattern (core technique)
meta=(BindWidget) tells UMG that a UPROPERTY in C++ maps to a widget of the same name
in the Widget Blueprint. The Blueprint compiler validates the name match at load time.
// MyHUDWidget.h
#pragma once
#include "Blueprint/UserWidget.h"
#include "MyHUDWidget.generated.h"
class UButton;
class UTextBlock;
class UProgressBar;
UCLASS()
class MYGAME_API UMyHUDWidget : public UUserWidget
{
GENERATED_BODY()
protected:
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
// Name in BP designer must match exactly:
UPROPERTY(meta=(BindWidget))
TObjectPtr<UButton> StartButton; // required — compile error if absent in BP
UPROPERTY(meta=(BindWidget))
TObjectPtr<UTextBlock> ScoreText;
UPROPERTY(meta=(BindWidgetOptional))
TObjectPtr<UProgressBar> HealthBar; // optional — null if not in BP, no error
// Bound anim (Blueprint-authored UMG animation):
UPROPERTY(meta=(BindWidgetAnim), Transient)
TObjectPtr<UWidgetAnimation> FadeInAnim;
UFUNCTION()
void OnStartClicked();
public:
void SetScore(int32 Score);
void SetHealthPercent(float Percent); // 0..1
};
// MyHUDWidget.cpp
#include "MyHUDWidget.h"
#include "Components/Button.h"
#include "Components/TextBlock.h"
#include "Components/ProgressBar.h"
void UMyHUDWidget::NativeConstruct()
{
Super::NativeConstruct();
if (StartButton)
{
StartButton->OnClicked.AddDynamic(this, &UMyHUDWidget::OnStartClicked);
}
}
void UMyHUDWidget::NativeDestruct()
{
if (StartButton)
{
StartButton->OnClicked.RemoveDynamic(this, &UMyHUDWidget::OnStartClicked);
}
Super::NativeDestruct();
}
void UMyHUDWidget::OnStartClicked()
{
// handle click
}
void UMyHUDWidget::SetScore(int32 Score)
{
if (ScoreText)
{
ScoreText->SetText(FText::AsNumber(Score)); // FText for display/localization
}
}
void UMyHUDWidget::SetHealthPercent(float Percent)
{
if (HealthBar)
{
HealthBar->SetPercent(FMath::Clamp(Percent, 0.f, 1.f));
}
}
Key rules:
- The C++ variable name must equal the widget name in the Blueprint designer; any mismatch
is a load/compile error (use
BindWidgetOptionalif the widget may not exist). OnClickedis aUPROPERTY(BlueprintAssignable)dynamic multicast delegate onUButton(Button.h:76); the bound function must be aUFUNCTION().- Remove
AddDynamicbindings inNativeDestructto prevent stale delegates.
Creating and showing widgets at runtime
// In a PlayerController or HUD:
UPROPERTY(EditAnywhere, Category="UI")
TSubclassOf<UMyHUDWidget> HUDWidgetClass; // assign the Widget BP in the editor
UPROPERTY()
TObjectPtr<UMyHUDWidget> HUDWidget;
void AMyPlayerController::BeginPlay()
{
Super::BeginPlay();
// CreateWidget<T>(owner, class) — owner can be World, PlayerController, or GameInstance
HUDWidget = CreateWidget<UMyHUDWidget>(this, HUDWidgetClass);
if (HUDWidget)
{
HUDWidget->AddToViewport(); // ZOrder 0 by default
// HUDWidget->AddToPlayerScreen(); // split-screen: add to a player's viewport slice
}
}
void AMyPlayerController::HideHUD()
{
if (HUDWidget)
{
HUDWidget->RemoveFromParent(); // hides; does not destroy (still held by UPROPERTY)
}
}
CreateWidgetis a templated free function (UserWidget.h:1819); it accepts aUWorld*,APlayerController*,UGameInstance*,UWidget*, orUWidgetTree*as owner.- Hold widget pointers in a
UPROPERTY()— without it the GC destroys the widget even if it is displayed (ue-memory-and-gc). RemoveFromViewportis deprecated since 5.1; useRemoveFromParent()instead.
Data binding & update strategy
| Approach | When |
|---|---|
| Push setters (recommended) | Call SetScore()/SetHealthPercent() from gameplay code when data changes — zero per-frame cost. |
| UMG Property Binding (legacy) | A function returning a value bound in the editor; re-evaluates every frame — avoid for many widgets. |
| MVVM Viewmodel plugin | Declare UMVVMViewModelBase with FieldNotify properties; view bindings update only on change. Best for larger data-driven UIs (UE 5.1+, still Beta in 5.8). |
Epic's hard rule: never raw property bindings — they poll every frame per widget.
Drive updates from gameplay delegates, and pull initial state once in NativeConstruct.
Full MVVM walkthrough (FieldNotify, UE_MVVM_SET_PROPERTY_VALUE, viewmodel design):
references/architecture-and-authoring.md.
Performance best practices (summary)
The rules agents most often need — deep dive with engine citations in references/performance-and-best-practices.md:
- One Canvas Panel at the root is fine; never nest Canvas Panels inside reusable
widgets (each canvas child gets its own layer ID → extra draw calls, broken batching).
Build templates from
Overlay/HorizontalBox/VerticalBox/GridPanel. - No Tick, no OnPaint, no property bindings — event-driven updates only.
- Wrap rarely-changing subtrees in
UInvalidationBox; mark per-frame widgets Volatile; reach forURetainerBox(phased render-to-texture) only after that. - Pool dynamic entries:
UListView/UTileView(virtualized + pooled) for lists,FUserWidgetPoolfor damage numbers/markers/toasts. - Hidden widgets still load and construct — every
WidgetSwitcherpage, everyTSubclassOfreference. Delete unused widgets; async-load rare screens via soft refs. - Prefer
CollapsedoverHidden(skips layout); set decorative widgets toHitTestInvisible; don't callSetVisibility/FText::Formatper frame. USpaceroverUSizeBoxfor spacing; never Scale Box + Size Box together (per-frame layout flip-flop); Rich Text only when really needed.- Animation cost tiers: material animation > BP tween > Sequencer anim > anything that changes layout.
- Profile with
stat Slate, Widget Reflector (Ctrl+Shift+W),Slate.ShowBatching,stat dumpframe -ms=0.1.
Input, focus, and input mode
- Override
NativeOnKeyDown,NativeOnMouseButtonDown, etc. for per-widget input handling. - For menus: switch input mode on the
PlayerControllerviaSetInputModeUIOnly/SetInputModeGameAndUI/SetInputModeGameOnlyand toggle the cursor. - For gamepad/multiplatform menus, see CommonUI below — plain UMG focus is painful to manage manually across platforms.
UWidgetComponent — 3D in-world widgets
UWidgetComponent (WidgetComponent.h:95) is a UMeshComponent that renders a
UUserWidget onto a render target, then displays it on a plane or cylinder in 3D space.
// In an actor's .h:
UPROPERTY(VisibleAnywhere)
TObjectPtr<UWidgetComponent> InteractPrompt;
// In constructor:
InteractPrompt = CreateDefaultSubobject<UWidgetComponent>(TEXT("InteractPrompt"));
InteractPrompt->SetupAttachment(RootComponent);
InteractPrompt->SetWidgetClass(PromptWidgetClass); // TSubclassOf<UUserWidget>
InteractPrompt->SetDrawSize(FVector2D(200.f, 80.f));
InteractPrompt->SetWidgetSpace(EWidgetSpace::World); // World or Screen
Access the live widget instance at runtime: InteractPrompt->GetWidget().
For player interaction with 3D widgets, pair with UWidgetInteractionComponent.
CommonUI (recommended for production menus)
The CommonUI plugin (Engine/Plugins/Runtime/CommonUI/) builds on UMG for structured,
multiplatform UI:
UCommonUserWidget—UUserWidget+ input-action binding (CommonUserWidget.h:33).UCommonActivatableWidget— adds activate/deactivate semantics and a back-navigation stack; the widget can turn on/off without being removed from the hierarchy (CommonActivatableWidget.h:43).- Input routing — only the topmost active activatable widget in a layer receives input; no need to manually manage focus across multiple overlapping menus.
- Style assets —
UCommonButtonBase,UCommonTextBlock, etc., separate style data from widget instances, making platform-specific theming practical.
Use CommonUI for any UI with menus, modals, or gamepad navigation. Base game-HUD widgets
on UCommonUserWidget; base menus on UCommonActivatableWidget.
Screen architecture — layer stacks (the Lyra pattern): production games register one
root layout with named layers, each a UCommonActivatableWidgetStack (or ...Queue for
modals) — e.g. UI.Layer.Game / GameMenu / Menu / Modal in Lyra's
PrimaryGameLayout. Screens are pushed/popped by layer tag instead of AddToViewport,
which gives correct Z-order, input routing to the topmost active widget, and free
back-button handling. Details and best practices:
references/architecture-and-authoring.md.
Slate layer (when and how)
UMG wraps Slate. For editor tools and highly bespoke widgets not achievable in UMG, author Slate directly.
// Minimal SCompoundWidget subclass — editor tool or plugin UI:
class SMyPanel : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SMyPanel) {}
SLATE_ARGUMENT(FText, LabelText)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs)
{
ChildSlot
[
SNew(STextBlock).Text(InArgs._LabelText)
];
}
};
Core Slate types:
SWidget(SlateCore/Public/Widgets/SWidget.h) — base; pure abstract (OnPaint:1771,ComputeDesiredSize:774,GetChildren:899).SCompoundWidget(SCompoundWidget.h) — singleChildSlot; base for most authored widgets.SLeafWidget— no children; for custom-drawn leaf elements.SNew(WidgetType)/SAssignNew(Ptr, WidgetType)— declarative construction macros (DeclarativeSyntaxSupport.h:37).
For game UI, stay in UMG/CommonUI — Slate skips UPROPERTY/GC and needs more boilerplate.
Gotchas
BindWidgetname mismatch — the C++ variable name must match the Blueprint widget name exactly; a mismatch is a compiler/load error. UseBindWidgetOptionalif the widget may be absent. (Widget.h:69-74).- Widget not in a
UPROPERTY— the GC destroys it even if still displayed; always hold in aUPROPERTY()member. RemoveFromViewportdeprecated (5.1+) — useRemoveFromParent().- Per-frame property bindings across many widgets → significant CPU cost; push updates from gameplay instead.
- Canvas Panels nested inside reusable widgets — each canvas child gets its own layer ID → extra draw calls across the whole screen. One canvas at the root only; templates use Overlay/boxes.
- Scale Box wrapping a Size Box (or vice versa) — the two fight over desired size and can flip layout every frame; let content size itself or pick one.
- Calling
SetVisibilityevery frame — it's surprisingly expensive (can invalidate layout); only call on actual state change. PreferCollapsedoverHidden, andHitTestInvisiblefor decorations. - Hidden widgets still load and construct — unused widgets in the tree and every page
of a
WidgetSwitcherpay full load/construct cost; delete leftovers, async-load rare screens. - Create/destroy churn for dynamic entries — use
UListVieworFUserWidgetPoolinstead; withFUserWidgetPool, callReleaseAllSlateResources()from the owner'sReleaseSlateResourcesor the pool leaks via circular refs. OnClickedhandler not aUFUNCTION()—AddDynamicsilently fails; the bound method must be markedUFUNCTION()with the matching signature.- Forgetting
SetInputMode/cursor — menus added to viewport that don't receive clicks usually have the wrong input mode on the owningPlayerController. - Gamepad/focus pain with plain UMG — use CommonUI for structured input routing across
menus and platforms (
ue-enhanced-inputfor input-action integration). - Building display text with
FString— useFText::AsNumber,FText::Format, etc. for localization (ue-core-types-and-containers). WidgetComponentnot visible — ensureDrawSizeis non-zero and collision/visibility settings are correct;EWidgetSpace::Screenignores world occlusion.NativeTicknever called —UUserWidgethasDisableNativeTickby default; the tick only runs if you overrideNativeTickand the widget has latent actions or Blueprint tick is set toAuto(EWidgetTickFrequency:UserWidget.h:117).
Version notes
RemoveFromViewportdeprecated in 5.1; replaced byRemoveFromParent(fromUWidget).UTextBlock/UButtondirect property access deprecated in 5.1/5.2 respectively; use getter/setter methods (GetText()/SetText(),GetStyle()/SetStyle()).- UMG Viewmodel (MVVM) plugin available since 5.1, still Beta in 5.8; stable enough for production with care.
- CommonUI ships in engine and is stable; Lyra uses it as the canonical game UI reference.
References & source material
Engine source (UE 5.8, under Engine/Source/):
Runtime/UMG/Public/Blueprint/UserWidget.h—UUserWidgetUCLASS:280,AddToViewport:342,AddToPlayerScreen:351,RemoveFromViewport(deprecated 5.1):358,NativeOnInitialized:1582,NativePreConstruct:1583,NativeConstruct:1584,NativeDestruct:1585,NativeTick:1586,CreateWidgetInstance(backingCreateWidget):1470-1474,EWidgetTickFrequency:117.Runtime/UMG/Public/Blueprint/WidgetTree.h—UWidgetTree,RootWidget:150,ConstructWidget<T>:106,ForEachWidget:78,FindWidget:34.Runtime/UMG/Public/Components/Widget.h—UWidgetUCLASS:216,BindWidget/BindWidgetOptionalmetadata enum:69-74.Runtime/UMG/Public/Components/Button.h—UButton:32,OnClickeddelegate:76,OnPressed:80,OnReleased:84,SetStyle:117,GetStyle:119.Runtime/UMG/Public/Components/TextBlock.h—UTextBlock:23,SetText/GetText(via Getter/Setter specifiers):31.Runtime/UMG/Public/Components/PanelWidget.h—UPanelWidget:14,AddChild:59,GetChildAt:36,GetChildrenCount:28.Runtime/UMG/Public/Components/WidgetComponent.h—UWidgetComponent:95,EWidgetSpace:25,GetWidget:207,SetWidget:214,SetWidgetClass:338.Runtime/SlateCore/Public/Widgets/SWidget.h—SWidget,ComputeDesiredSize:774,GetChildren:899,OnPaint:1771.Runtime/SlateCore/Public/Widgets/SCompoundWidget.h—SCompoundWidget:21,ChildSlot:113.Runtime/SlateCore/Public/Widgets/DeclarativeSyntaxSupport.h—SNew:37,SAssignNew:41.Engine/Plugins/Runtime/CommonUI/Source/CommonUI/Public/CommonUserWidget.h—UCommonUserWidget:33.Engine/Plugins/Runtime/CommonUI/Source/CommonUI/Public/CommonActivatableWidget.h—UCommonActivatableWidget:43,ActivateWidget:52,DeactivateWidget:55.
Official docs (UE 5.8, verified):
- Creating User Interfaces — https://dev.epicgames.com/documentation/unreal-engine/creating-user-interfaces-with-umg-and-slate-in-unreal-engine
- Slate UI Framework — https://dev.epicgames.com/documentation/unreal-engine/slate-user-interface-programming-framework-for-unreal-engine
- Widget Type Reference — https://dev.epicgames.com/documentation/unreal-engine/widget-type-reference-for-umg-ui-designer-in-unreal-engine
- Plugins for UI Development — https://dev.epicgames.com/documentation/unreal-engine/plugins-for-ui-development-in-unreal-engine
- UMG Viewmodel — https://dev.epicgames.com/documentation/unreal-engine/umg-viewmodel-for-unreal-engine
- Common UI Plugin — https://dev.epicgames.com/documentation/unreal-engine/common-ui-plugin-for-advanced-user-interfaces-in-unreal-engine
- UMG Best Practices — https://dev.epicgames.com/documentation/en-us/unreal-engine/umg-best-practices-in-unreal-engine
- Optimization Guidelines for UMG — https://dev.epicgames.com/documentation/en-us/unreal-engine/optimization-guidelines-for-umg-in-unreal-engine
- Invalidation in Slate and UMG — https://dev.epicgames.com/documentation/en-us/unreal-engine/invalidation-in-slate-and-umg-for-unreal-engine
- UMG Safe Zones — https://dev.epicgames.com/documentation/unreal-engine/umg-safe-zones-in-unreal-engine
Deep-dive references in this skill:
- references/userwidget-and-binding.md — full
UUserWidgetlifecycle, BindWidget internals, NativeOnInitialized vs NativeConstruct, animation binding (BindWidgetAnim), andGameViewportSubsystem. - references/common-widgets-and-layout.md — every common leaf widget and panel type, slot system, dynamic widget creation from C++.
- references/widget-component-and-input.md —
UWidgetComponentdeep dive,UWidgetInteractionComponent, input mode management, focus. - references/slate-layer.md —
SWidgethierarchy, declarative syntax,SCompoundWidgetauthoring, Invalidation, and when to use Slate vs UMG. - references/performance-and-best-practices.md —
invalidation boxes/retainer boxes/volatility, event-driven updates vs property bindings,
widget pooling (
FUserWidgetPool, ListView), loading/construction costs, Canvas Panel and layout do/don'ts, animation cost tiers, hidden costs (SetVisibility,FText::Format), and the profiling toolbox. - references/architecture-and-authoring.md —
CommonUI activatable layer stacks (the Lyra pattern), decoupling widgets from gameplay,
MVVM viewmodels (
FieldNotify,UE_MVVM_SET_PROPERTY_VALUE), DPI scaling and resolution independence, safe zones, and texture/slot-sizing authoring rules.