Reference · Updated 2552.07.26.14.59
SL Button Widget — Intended Design & Authoring
How the menu button is meant to be built and used. Written because the split between the C++ base class, the styled widget asset, the bound label widget, and the per-instance capti
How the menu button is meant to be built and used. Written because the split between the C++ base class, the styled widget asset, the bound label widget, and the per-instance caption is easy to trip over — most people’s first instinct (type text straight into the text block) fights the design and the text keeps vanishing.
Read alongside:Docs/UISystem.md(CommonUI plumbing + footguns),Docs/MenuSystemPlan.md§5.0a
(button asset prerequisite in the build order), Docs/CodexHandoff_WidgetTrees.md (step-by-step editor
spec for the trees).
#1. The three layers (and why there are three)
A menu button is not one thing. It’s a stack, each layer with one job:
| Layer | What it is | Job | You place / edit it… |
|---|---|---|---|
USLButtonBase | C++ class (Public/UI/SLButtonBase.h) | Behavior: label sync, uppercase, text-style sync, focus/hover → description. No visuals, no style, no label of its own. | Never directly. It’s Abstract. |
WBP_SL_Button | Widget BP asset (/Game/SystemLink/UI/Menus/), parented to USLButtonBase | The thing menus place. Carries the Style (Style_ButtonMedium) and owns the label text block. | Author its tree once. |
| A placed instance | A WBP_SL_Button dropped into WBP_SL_PauseMenu, etc. | One concrete button on one screen. | Set its caption per screen. |
Why not collapse them? Because USLButtonBase’s CDO has Style = None and no internal label — drop the raw C++ class into a menu and you get an invisible, label-less widget (CommonUI even warns “must have a Style set”). The style + label live on WBP_SL_Button so every menu reuses one styled, labeled button and never re-solves that. See MenuSystemPlan.md §5.0a.
#2. How the label actually flows
Two named pieces, and they are not the same:
ButtonText— aCommonTextBlockinsideWBP_SL_Button’s tree, named exactlyButtonText.
This is the widget that draws the caption. It’s bound in C++ via UPROPERTY(meta=(BindWidgetOptional)).
- ButtonDisplayText — an FText property on the button (SystemLink | UI category), set **per placed |
|---|
instance**. This is the caption value.
The wiring that connects them lives in USLButtonBase:
void USLButtonBase::NativePreConstruct()
{
Super::NativePreConstruct();
SetButtonText(ButtonDisplayText); // pushes the property → into the ButtonText block
}
SetButtonText applies bUseUpperCaseForButtonText and the current CommonTextStyle, then calls ButtonText->SetText(...). So the flow is always:
per-instance ButtonDisplayText ──(NativePreConstruct)──► ButtonText block ──► on screen
ButtonDisplayText is the single source of truth for the caption. That’s deliberate — it’s also what the runtime SetButtonText(...) path and the uppercase/text-style logic assume.
#3. The authoring workflow (do it this way)
A. Build WBP_SL_Button’s tree — once.
- Add a
CommonTextBlockto the tree, named exactlyButtonText(case-sensitive).
- Center it (Horizontal + Vertical Alignment = Center).
- Do not type a caption into its
Textfield. Leave it empty. (SetStyleon the button itself if not
already — Style_ButtonMedium.)
B. Per screen, per button — set the caption on the button, not the block.
- Select the placed WBP_SL_Button (the root, i.e. the USLButtonBase), find **SystemLink | UI → |
|---|
Button Display Text**, and type the caption there (Resume, Settings, …).
- Optionally toggle
Use Upper Case For Button Textand setButton Description Text(surfaces on
focus and hover — controller-reachable, footgun 6.19).
That’s it. The caption shows in the designer preview and at runtime, and it won’t get wiped.
#4. Gotchas (the ones that actually bite)
- “My default text keeps disappearing when I type it into the text block.” Expected.
NativePreConstruct
runs continuously in the designer and overwrites ButtonText with ButtonDisplayText (empty by default), so it stomps anything you typed into the block’s Text field. Fix: set Button Display Text on the button instead. The block is a render target, not where you author copy. (This doc exists because of exactly this confusion — 2026-07-08.)
- Name the block
ButtonText, nothing else.BindWidgetOptionalbinds by name with no warning on a
mismatch — a block named Text_Label, Label, Text, etc. binds to null, SetButtonText early-returns, and the button is silently blank forever. (Footgun bindwidgetoptional_silent_noop; same class of bug that bit GrenadeIndicator.) ⚠ Note: an earlier CurrentFocus.md “NEXT” step said add a Text_Label block — that was wrong; it must be ButtonText to match the C++ property.
BindWidgetOptional, notBindWidget. The bind is optional on purpose — a button with no label (icon-only)
is legal. The cost is you don’t get a compile error when the name’s wrong; you get silence. Match the name exactly and it’s a non-issue.
- Never place the raw
USLButtonBase(or CDO) in a menu.Style = None+ no label = invisible widget +
CommonUI style warning. Menus place WBP_SL_Button, always.
- Runtime caption changes go through
SetButtonText(FText)(BlueprintCallable), not by poking the block —
it keeps uppercase + text-style in sync. Setting ButtonDisplayText then re-running construction also works.
#5. Quick reference
| I want to… | Do this |
|---|---|
| Set a button’s label | Set Button Display Text on the placed WBP_SL_Button |
| Change a label at runtime | Call SetButtonText (BlueprintCallable) on the button |
| Make labels all-caps | Check Use Upper Case For Button Text on the button |
| Add a focus/hover description | Set Button Description Text; bind OnDescriptionChanged from a shared description panel |
| Add a new styled button variant | Duplicate WBP_SL_Button, change its Style (e.g. Style_ButtonLarge) |
| Name the label block | Exactly ButtonText — case-sensitive |
Files: Public/UI/SLButtonBase.h · Private/UI/SLButtonBase.cpp · asset /Game/SystemLink/UI/Menus/WBP_SL_Button. </content> </invoke>