Reference · Updated 2552.07.26.14.59

Command Bar — Back & Accept (Hardcoded Approach)

How SystemLink's on-screen prompt strip (◀ Back, Ⓐ Select) is built. Decision (2026-07-20): hardcode it. The bar only ever needs Back and maybe Accept, so we skip CommonUI's dynami

How SystemLink's on-screen prompt strip (◀ Back, Ⓐ Select) is built. Decision (2026-07-20): hardcode it. The bar only ever needs Back and maybe Accept, so we skip CommonUI's dynamic CommonBoundActionBar and build a fixed strip. The dynamic bar is reframed as a MenuKit-product upgrade (see the last section).


#1. The key fact: the bar is display-only

Back and Accept already work without any command bar:

  • B closes the menu via bIsBackHandler (set in the USLScreenWidget constructor).
  • A clicks the focused button via CommonUI's default click action.

The command bar changes nothing about behavior — it only shows the player which buttons do what. That's why hardcoding the display is safe: we're not reimplementing input, just drawing two labels + glyphs.

#2. Design: two building blocks

AssetTypeRole
WBP_SL_CommandPromptUserWidgetOne prompt = glyph + label. Reusable, two exposed vars.
WBP_SL_CommandBarUserWidgetA HorizontalBox holding the prompts (Back, Accept).

Use a CommonActionWidget for the glyph — not a plain image or the letter "B". The CommonActionWidget auto-resolves the correct controller glyph for the current gamepad from the input action. That is the one thing worth keeping from CommonUI; everything else (the router, dynamic spawning) we drop.

The command bar is gamepad-only (decision 2026-07-20). On mouse/keyboard the whole prompt strip is
hidden (WBP_SL_CommandBar collapses its prompts when the active device is KBM), so no keyboard glyphs are
needed — there is no CD_SL_KBM. See "Gamepad-only visibility" below. (We briefly authored a CD_SL_KBM
key-cap set, then removed it once the bar was hidden on KBM — the glyphs would never render.)

#Step 1 — Build WBP_SL_CommandPrompt (the reusable prompt)

  1. Create WBP_SL_CommandPrompt (/Game/SystemLink/UI/Menus/, parent UserWidget).
  1. Tree:

   HorizontalBox (root)

   ├─ Glyph   [CommonActionWidget]

   ├─ Spacer  (size ~8)

   └─ Label   [CommonTextBlock]     (use Style_… text style to taste)

  1. Add two instance-editable variables (eye icon open):
    • InputAction — type Input Action (object reference, i.e. UInputAction).
    • PromptLabel — type Text.
  1. In the PreConstruct graph:
    • Glyph → Set Enhanced Input Action (InputAction) — resolves the glyph brush.
    • Label → Set Text (PromptLabel).
  1. Compile + Save.
Doing this in PreConstruct means the designer preview updates per instance, and the runtime widget sets
itself up on construction — see the timing note in Step 3.
Why no DT_SL_InputActions row? CommonActionWidget resolves the glyph either from an
EnhancedInputAction (a UInputAction) or from the legacy InputActions DataTable array — never both.
UCommonActionWidget::GetIcon() is a strict ternary: with enhanced input enabled (it is —
bEnableEnhancedInputSupport=True), a set EnhancedInputAction wins and the DataTable array is ignored.
So we set only the UInputAction; DT_SL_InputActions is the legacy alternative, not an extra requirement.
(The glyph brush comes from CD_SL_Xbox; the key comes from IMC_SL_UI — the DataTable is not in this path.)
If you ever see a prompt with both an InputActions row and an EnhancedInputAction set, the row is inert.

#Step 2 — Build WBP_SL_CommandBar (the strip)

  1. Open WBP_SL_CommandBar (already exists as the old placeholder — replace its contents).
2. Delete the placeholder TextBlocks (`B BACK= OPTIONSR SOCIAL`).
  1. Tree:

   HorizontalBox (root, Right-aligned or Fill to taste)

   ├─ WBP_SL_CommandPrompt  "Accept"   InputAction = IA_SL_UI_Confirm,  PromptLabel = "Select"

   ├─ Spacer (size ~24)

   └─ WBP_SL_CommandPrompt  "Back"      InputAction = IA_SL_UI_Back,     PromptLabel = "Back"

  • Order right→left is a Halo convention (Back rightmost). Flip if you prefer.
    • Set each prompt's two exposed vars in the Details panel per the table above.
  1. Style the root (padding, background) with the angled Halo panel material if desired.
  1. Compile + Save.
Labels: IA_SL_UI_Back.ActionDescription is "Back" and IA_SL_UI_Confirm.ActionDescription is
"Select". You can either type the label into PromptLabel (simplest, done above) or drive it from the
action's description later. Hardcoding the text here is fine.

#Step 3 — Place it so it appears with a menu

The bar must be present/constructed while a menu is up (that's when the glyph can resolve — see the footgun). Two options; A is recommended for reliability and simplicity.

Drop WBP_SL_CommandBar into the bottom of each screen's tree (WBP_SL_PauseMenu, WBP_SL_Settings, …), anchored bottom-center or bottom-right.

  • Pro: constructed exactly when the screen activates → the UI input context (IMC_SL_UI) is already
  • applied, so the glyph resolves correctly. Visible iff the screen is visible — no visibility logic.

  • Pro: per-screen freedom — a confirm modal can show Select + Back, a pure info screen just Back.
  • Con: one instance per screen (trivial — it's a single dropped widget; the vars are already set inside
  • WBP_SL_CommandBar).

#Option B — one shared bar in the layout

Place WBP_SL_CommandBar in WBP_SL_PrimaryGameLayout's root Overlay (Overlay_54), as the last child (top z-order), Vertical = Bottom. Then in the layout graph:

  • Bind MenuStack → On Displayed Widget Changed: set WBP_SL_CommandBar visibility to Visible when a widget
  • is active, Collapsed when none.

  • Also re-resolve the glyph on show (call the prompt's Glyph → Set Enhanced Input Action again, or a
  • small RefreshGlyph function) — a bar constructed before IMC_SL_UI is applied may cache an empty glyph. This extra refresh is the price of the shared approach, and the reason A is recommended.

#Step 4 — Verify in PIE (the gate)

  1. PIE with a gamepad.
  1. Press Start → pause menu opens.
  1. Expected: the strip shows Ⓐ Select and Ⓑ Back at the bottom.
  1. Press B → menu closes → strip goes away (with the screen, Option A).
  1. On keyboard/mouse (move the mouse to flip the active device): the whole strip is hidden.

Pass criteria: strip appears with the menu on a gamepad (both glyphs render), is hidden on KBM, and B closes.


#Gamepad-only visibility

The command bar is meaningless on mouse/keyboard (the player clicks directly and Esc is obvious), so it's hidden whenever the active device is KBM. This is driven off the UCommonInputSubsystemnot the input actions or the CommonActionWidget:

  • OnInputMethodChanged — a BlueprintAssignable delegate on the subsystem; fires with the new
  • ECommonInputType when the device switches.

  • GetCurrentInputType() — reads the device right now (for the initial state).

WBP_SL_CommandBar graph (Event Construct):

  1. Get Owning Local PlayerGet Local Player Subsystem (Class = CommonInputSubsystem). *(That node is
  2. hidden if the palette's Context Sensitive box is checked — uncheck it, or hold Ctrl while searching.)*

  1. Is Valid guard → Bind Event to On Input Method Changed → a custom event.
  1. Both the custom event and a one-time Get Current Input Type on construct funnel through a single
  2. SetInputType/refresh function that sets the prompt visibility: Gamepad → Visible, everything else → Collapsed. Call it from both paths so the initial state is right (the delegate only fires on change).

Do the bind on Event Construct, not Pre-Construct — Pre-Construct also runs in the designer with no valid
local player. Make the refresh a symmetric if/else (re-shows on returning to a pad, not just hide-on-KBM).

Because the bar never draws on KBM, no CD_SL_KBM glyph set is needed — only CD_SL_Xbox is registered.


#Troubleshooting / footguns

  • Glyph blank on gamepad too → the CommonActionWidget resolved before IMC_SL_UI was live. CommonUI
  • resolves glyphs via QueryKeysMappedToAction, which only sees currently-applied input contexts. Option A (embed in screen) avoids this because the screen applies Menu input on activate before its children resolve. If on Option B, add the RefreshGlyph call from Step 3B.

  • Designer preview is not proof. UCommonActionWidget::GetIcon() has an editor-only branch that renders
  • DesignTimeKey and bypasses the runtime path — it will show a perfect glyph even when the runtime one is broken. Validate in PIE only.

  • Glyph is the wrong button → check the prompt's InputAction var points at the right IA_SL_UI_* action,
  • and that action is mapped in IMC_SL_UI.

  • Nothing shows at all (Option B) → visibility binding not firing, or the bar is under an opaque menu
  • background (it's not the last child of the root Overlay).

  • Don't wire click/behavior into these prompts. They are pure display. B and A already function via
  • bIsBackHandler / the default click action. A command prompt is not a button.

#Cleanup

  • WBP_SL_BoundActionButton (built for the dynamic bar) is not used by this approach. Keep it parked for
  • the MenuKit upgrade below, or delete it if you want the tree clean (confirm zero referencers first).

  • The old placeholder text in WBP_SL_CommandBar is replaced in Step 2.

#Later: the dynamic bar as a MenuKit upgrade

For SystemLink the game, hardcoded Back/Accept is the right call — simpler, and it moves us toward the Settings-menu gate instead of re-fighting the CommonUI glyph/router plumbing.

For MenuKit the Fab product, the dynamic CommonBoundActionBar is the more valuable, expected form — buyers' screens have varied actions and want the bar to populate itself. The groundwork already exists (WBP_SL_BoundActionButton tree is built; USLScreenWidget sets bIsBackActionDisplayedInActionBar), so swapping the hardcoded strip for the dynamic bar is a self-contained later task, not a rewrite. Treat it as a MenuKit feature, not a SystemLink blocker.

Prior revision of this doc described that dynamic-bar wiring in full; it's preserved in git history if the
MenuKit upgrade needs it.