Making UE5 UMG Usable: Focus Order and Reviewing Update Cost

Created: 2025-12-12Last updated: 2026-09-06

The menu selection jumps around, and things get heavy while it is open. We investigate both separately: set Navigation on a three-button menu, move a count display from per-frame to on-demand, and learn how to choose between Invalidation Box and Retainer Box.

A menu you can click with the mouse skips the button you wanted when you press down on a gamepad. On another screen, the game gets heavy only while that screen is open. Once the UI looks right, ease of operation and the cost of running it start to matter.

Investigate these two separately. The first half decides which button receives input and in what order focus moves ; the second half checks how often the display updates and what that costs . We use the same small menu and watch what changes when you change what.

Deciding where the menu sends input, kept separate from when the marker count is re-checked

What You'll Learn

  • The difference between focus, mouse Hover, and the selection outline
  • Deciding button order with Explicit and Stop
  • Moving a per-frame update to an on-demand update
  • How to measure before and after, and what Invalidation and Retainer do

Sponsored

Focus is where key input goes

Focus is the UI element receiving keyboard or gamepad input. When a button has focus, Enter or the gamepad's accept button presses it. Direction keys move from there to the next selection.

Hover , meanwhile, is the state where the mouse pointer is over an element. The mouse can sit over "Controls" while key focus is on "Resume".

Even with the mouse over Controls, if focus is on Resume, Enter presses Resume

Having focus and showing it with an outline or color are also separate. Selection can move without you noticing, because the appearance did not change. Making which button receives input visible first makes everything easier to investigate.

Preparation: expanding a two-button menu to three

We use WBP_PadMenu from the gamepad support article. It has "Resume" and "Controls", opens with the M key or the gamepad's Menu/Start, and returns to the game when you press "Resume". If you have not built it yet, start from the menu creation in that article.

Here we add elements to that Widget. Keep using BP_InputPractice and PadMenuWidget for opening and closing. We do not add pausing game time. Focus and display updates are checked under these conditions first.

In the Designer, arrange the existing Vertical Box in this order. Duplicating HelpButton for CountButton and changing the name and label keeps things consistent.

ElementNameLabel / settings
TextFor the headingMenu
Button + TextResumeButtonResume
Button + TextHelpButtonControls
Button + TextCountButtonRefresh count
TextHelpTextEmpty at first. Where the existing control text goes
TextCount headingMarkers
TextCountText"Not counted" at first. Is Variable on

Set all three Buttons to Is Variable and Is Focusable on , Visibility Visible, and Is Enabled on. Is Focusable decides whether it can become the key-input target; Is Enabled decides whether it accepts operation.

So the buttons do not overlap, widen the Vertical Box's height to about 300, set children's Size to Auto, and set each Button's Padding to about 8. Keep the center anchor and Alignment (0.5, 0.5), and check the arrangement in the Designer. Layout fundamentals are covered in UMG Basics.

Adding CountButton and CountText to the existing menu, creating the refresh button and a place to show the marker count

While practicing, set Render Focus Rule to Always under "Project Settings" → "User Interface". That draws an outline around the focused element. Move the mouse off the buttons and watch the outline move with the keys.

Naming the next button with Navigation

Navigation is the setting for how focus moves when you press a direction key or Tab. Select the Button itself in the Designer and open Navigation in the Details. Set it on the Button that receives input, not on the Text inside it.

Start with these three rules.

RuleBehaviorWhen to use it
EscapeMoves that way and automatically finds the next selectable elementLayouts you can leave to auto search
ExplicitMoves to the element you specifyWhen you want the next button fixed
StopDoes not move that way and stays on the current elementStopping at the menu's edge

Escape is not a setting for the keyboard Esc key. It is a Navigation rule name. Auto search works for straightforward layouts, but when the destination matters in a complex arrangement, Explicit makes your intent verifiable.

Decide the vertical movement

Our menu is selected top to bottom and stops at both ends. Enter the next Button's name in the destination field that appears when you choose Explicit. If no candidates appear, check the name and Is Variable and compile.

Selected ButtonUpDown
ResumeButtonStopExplicit → HelpButton
HelpButtonExplicit → ResumeButtonExplicit → CountButton
CountButtonExplicit → HelpButtonStop

Set Left and Right to Stop on all three, since nothing moves horizontally.

So Tab works too, set Next to the same as Down and Previous to the same as Up. Next is the path Tab takes forward, and Previous is Shift+Tab backward. Deciding only up and down can leave Tab in a different order, so check them separately.

Advancing Resume, Controls, Refresh count and back again, stopping at the vertical ends and horizontally

Change one thing and confirm the destination

Compile, save, Play, and open the menu with M. If down moves "Resume → Controls → Refresh count" and pressing down again at the bottom stops, the settings are right. Up goes back in reverse.

Now stop Play and temporarily change only ResumeButton's Down to CountButton. Open it again and press down, and it should skip "Controls". That shows Explicit moves to the element you named, even if something else looks adjacent . Set it back to HelpButton afterwards.

When a destination surprises you, first look at "which rule is set in which direction on the current button". If it still names a Button that became hidden or disabled, reassign it to a valid target for that screen state.

Selecting the first button, and why it sometimes fails

Navigation moves from wherever you already are to the next place. The selection right after the menu opens is decided separately.

FocusFirstButton from the gamepad article is a function inside WBP_PadMenu that calls Set User Focus . Target is ResumeButton and Player Controller is Get Owning Player. It says "move this player's input target to this Button".

FocusFirstButton calls Set User Focus with ResumeButton as Target and Get Owning Player as Player Controller

Keep this order on the opening side.

  1. Create Widget for WBP_PadMenu and store it in PadMenuWidget.
  2. Display that same Widget with Add to Viewport.
  3. Set Input Mode UI Only to direct input to the UI and show the cursor.
  4. Finally, call that Widget's FocusFirstButton.

If work that hands focus to another Widget runs after this order, your setting is overwritten. Check not only "did I place Set User Focus" but what runs after the call .

SymptomWhere to check
No outline right after openingFocusFirstButton's exec wire, ResumeButton's Is Focusable, Render Focus Rule
Clickable with the mouse but not with keysThe input mode for UI, and focus on the first Button
Set User Focus does not select itWhether Target is the actual Button, whether it is shown and enabled, whether the Controller matches Owning Player
It is selected once, then jumps awayA later input-mode change, another Set Focus, or the screen being rebuilt
Cannot select after restoring from hiddenFocus assignment after returning to Visible, and Navigation destinations

With UI Only here, the Widget receives operation after opening. Rather than also closing with the game's M key, we close with ResumeButton and return to Game Only. Game and UI lets the game also receive input the UI did not handle, but it does not guarantee that keys the UI handled reach the game.

Sponsored

Split UI cost into update work and display work

Next is display updates for the same menu. Even when the number on screen does not change, the same search or calculation may be running behind it every frame.

The marker count is 3 either way, but checking every frame calls it far more often than checking on a Refresh count press

A frame is one round of the game updating the screen. Calling work from a Widget's Event Tick repeats it each frame it is displayed. Bindings set on Text or Percent also read values repeatedly. Even small work adds up as element count and call count grow.

Meanwhile, UMG itself has work to do: calculating element arrangement and size and preparing the information needed for drawing. There is GPU work to actually draw it too. Cutting only your own Blueprint work does not change overall speed much if those costs dominate.

Start by asking whether you need to repeat the same calculation without the appearance changing. Measure that effect first, then move on to element count and drawing mechanisms.

Hands-On: updating the count only when needed

We show the number of markers placed in the level on the menu. What we count here is Actors existing in the world . Keep that separate from the number of items the player carries.

1. Place three markers to count

Create a Blueprint with Actor as its parent and name it BP_UIPracticeMarker . Add a Static Mesh Component, choose Cube, and set Collision Presets to NoCollision. Leave physics simulation off.

Turn off Start with Tick Enabled in Class Defaults; this Actor does no per-frame work. Compile, save, and place three where you can see them in the level. This is a counting exercise for placed Actors, so we do not add spawn or destroy logic yet.

2. Build a function that displays the marker count

Create a function RefreshPracticeMarkerCount in WBP_PadMenu with no inputs or outputs.

  1. Connect a white exec wire from the function entry to Get All Actors Of Class, and set Actor Class to BP_UIPracticeMarker.
  2. Place Length from Out Actors.
  3. Convert Length's result from integer to Text and pass it to Set Text's In Text.
  4. Place CountText with Get and connect it to Set Text's Target.
  5. Connect Get All Actors Of Class's white exec output to Set Text.

Get All Actors Of Class searches for Actors of the given class and returns an Array . An array is a container holding multiple values. Reading its element count with Length gives 3 here.

To turn a number into display Text , drag from Length and choose the integer To Text node. The search runs on the white exec wire, but Length and the type conversion are value nodes and take no white wire.

Counting Get All Actors Of Class's Out Actors with Length and converting the integer to display text with To Text

Now wire the execution order for that calculation.

Running exec from the function entry through the search node to Set Text, with CountText as Target and To Text's result as In Text

The two diagrams show the same function's wiring in parts. Place only one Get All Actors Of Class and connect both the data and exec wires.

Do not add a Binding to CountText's Text; update it with this function's Set Text.

3. Call it every frame once, and watch how often it runs

In WBP_PadMenu's event graph, call RefreshPracticeMarkerCount from Event Tick. Compile, Play, and open the menu. If 3 appears under "Markers", the chain from search to display works.

The display stays at 3, but the work repeats. To confirm, temporarily insert a Print String at RefreshPracticeMarkerCount's entry with In String Count refreshed , Print to Screen off, and Print to Log on. Open the menu and the same line repeats in the Output Log. Keep this check brief.

That log is for confirming it gets called . Log output has a cost too, so remove it before measuring performance.

4. Move it to opening and to the refresh button

Stop Play and disconnect the white wire from Event Tick to the function. Instead call the same RefreshPracticeMarkerCount from two places.

You can add CountButton's click event by selecting that Button in the Designer and pressing "+" on On Clicked under Details → Events.

When it is calledPurpose
WBP_PadMenu's Event ConstructShow the initial count on the created menu
CountButton's On ClickedRe-check the count when "Refresh count" is pressed

If Event Construct already has work, continue at its end. Since opening creates the Widget each time here, the first count runs when you open it. Event Construct can also re-run when the display hierarchy is rebuilt; it is not an event guaranteed to fire exactly once in a Widget's lifetime.

Removing the call from Tick and calling the same counting function from Event Construct and CountButton's On Clicked

Open the menu again and 3 appears. Waiting adds no log lines, and pressing "Refresh count" adds one. The displayed number is the same, but we reduced how often it is checked. Remove the Print String afterwards.

The timeline moving a per-frame count to menu open and refresh button press

Stop Play, add one more marker, and open it again to see 4. In this exercise the marker count never changes at runtime, which is exactly why checking it while you wait was unnecessary.

In a real game where inventory or HP changes, notify the UI from the work that changed it. Combining an initial display with later change notifications leads into the HP bar hands-on.

Measure before and after under the same conditions

Cutting call count and making the whole game visibly faster are different claims. With three markers the work is tiny, and the time difference can vanish into measurement noise.

When investigating a real screen with real cost, work in the order measure → change one thing → re-measure under the same conditions .

  1. Remove debug logs such as Print String.
  2. Run stat unit in the console during Play. Display it before opening the menu in UI Only.
  3. Stand still in the same place, open the menu with the same camera and window size, and read Game and the rest.
  4. Stop Play and change only the update work you are investigating.
  5. Reopen the menu the same way, observe for a while, and compare.

Game is the game thread's processing time . It is CPU-side time where most of the game runs, not a UI-only number. The unit ms is milliseconds, one thousandth of a second. Do not judge from one instant; compare several times with the same actions.

Measure with stat unit, change one thing, and measure again from the same place with the same camera and window size

For this exercise, compare the Tick version against the Construct/click version. Comparing only before and after opening the menu mixes in other costs such as Widget creation. On screens that pause, do not treat "game paused" versus "game running" as your before-and-after comparison.

If you see no difference, there is no need to force a conclusion that it improved. To chase which work takes time in more detail, move on to the Unreal Insights article. After getting a sense of the trend in the editor, confirm on your shipping target too.

Sponsored

Choosing between Invalidation Box and Retainer Box

Even after tidying update frequency, screens with many elements pay for layout and draw preparation. That is when you consider mechanisms that cache calculated information or drawn results. A cache stores what you built once so you can use it again.

Invalidation Box reuses layout and draw information, while Retainer Box draws the whole UI into one image and uses that

Invalidation Box: reusing information for parts that do not change

Invalidation Box stores the information used to lay out and draw child Widgets. While nothing changes it reuses that information, recalculating only when something needs it.

The mark saying "the stored information is stale" is invalidation . It does not mean the UI became disabled and unusable.

A panel with lots of descriptive text and fixed icons is a good candidate. Select that group in the Designer, wrap it with an Invalidation Box from "Wrap With", and compare before and after on the same screen. Areas that add and remove elements every frame, or continuously change size, cannot reuse information for long.

You can also mark just the frequently changing small elements as Is Volatile . That treats their draw information as rebuilt each time, separating them from the static parts around them. It is not a setting for making the whole screen volatile.

If your project has Global Invalidation enabled, the whole window uses that mechanism. Individual Invalidation Box caching is not used inside it, so check the Slate.EnableGlobalInvalidation setting before adding a Box.

Retainer Box: drawing UI into a single image

Retainer Box draws child UI into an image destination called a Render Target and then displays that. Think of it as gathering into one picture instead of drawing everything directly to screen.

You can also adjust which frames update that picture. For a display where changes need not be instantaneous, wrap it in a Retainer Box in the Designer and set Render Rules to the following combination to try different update intervals.

SettingValue to tryMeaning
Render On PhaseOnUpdate on the specified frame phase
Phase Count3Split the update order into three
Phase0Update on phase 0 of those
Render On InvalidationOffDo not use extra updates from child change requests in this comparison

With these settings, periodic updates land once every three frames. Phase is an ordering, not seconds. If the game's frame rate changes, so does the real-time interval. Turning Render On Invalidation on also updates on child change requests, so updates happen outside this cycle too.

An example cycle with Phase Count 3 and Phase 0. Frames 0 and 3 redraw, and the previous picture shows in between

Spacing out updates can make changes appear delayed. Selection outlines and press feedback especially become hard to operate when delayed. There is no need to wrap our whole three-button menu in a Retainer just to try it.

A Retainer uses extra image memory and costs work when redrawing. Decide whether you want to batch draw calls, thin out updates, or add material effects like noise, and choose accordingly. "Invalidation Box helped little, so replace everything with Retainer" is not how to choose.

Bonus: checks as you expand the screen

Separate hiding from not creating

Hidden and Collapsed are display states. Collapsed also frees the layout space, but it does not free the memory of a Widget you already created. Creating every tab and list up front and hiding them leaves the creation and memory cost behind.

For long lists, consider mechanisms like List View that build mostly the visible rows. Wrapping in a Scroll Box does not automatically skip creating every entry.

Try the cases where the selection disappears

After closing a settings panel, return focus to the button that opened it. After deleting the selected entry, move to a remaining neighbor. Pair work that changes the display with work that decides the next selectable place like this.

If you want to show focus with color, the standard Button Style's Hovered alone cannot represent gamepad selection. Query the selection state with Has User Focus and reflect it in the appearance. Confirm movement and confirmation work with the Always outline first, then build it out.

Widget Reflector also helps investigate which Widgets actually compose the screen. It helps trace the hierarchy of displayed elements once the menu structure gets complex.

Extending to a pause screen

The pause menu article adds stopping and resuming game time on top of opening, closing, and input targeting. The Navigation we set here is still needed once pause is added. First get the loop of open with M, select, and walk again via "Resume" working, then add time control.

Summary

Focus problems get easier to investigate when you separate "who receives input first" from "where it moves next". Cost problems come down to finding work that repeats even though the display is unchanged, moving it to the right moment, and then measuring.

First confirm you can select all three buttons in order, and that counting is called only on menu open and refresh press. On top of that, applying Invalidation or Retainer where the real cost is keeps your goal and its effect in view.

Reference: Navigation rules, Set User Focus, UMG optimization guidelines, UI Invalidation, Retainer Box.

Unreal Engine Notes in this section98