Choice widgets¶
When a dialogue reaches a Choice line, Libretto spawns a UI widget that offers the player the available replies, waits for a selection (or for the choice timer to run out), and jumps to the chosen reply. This page explains how to configure which widget is used and how to build your own.
Configuration¶
The widget class used by default for dialogue choices is set in Project Settings ▸ Libretto, in the UI section.

- Widget Choice Default Class — the choice widget spawned for a Choice that has no matching
flag. If you leave it empty, choices fall back to the widget shipped with the plugin,
DefaultLBRDialogueChoiceWidget_WBP. That default is a good starting point: duplicate it to use as a base or as a worked example. - Widget Choice Map Class — lets you pick a different widget for specific choices based on a gameplay flag present on the Choice line. The order of the entries in the map defines priority: when a line carries several flags, the first flag that appears in the map decides which widget class is used.
Available replies¶
A reply is considered available when all of the following hold:
- It has never been selected, or it has been selected but Do Once is unticked.
- It has no Display Condition, or the condition evaluates to
true. - It has no Availability Condition, or the condition evaluates to
true.
Note
Display Condition and Availability Condition differ in how a failing condition is
handled. A failing Display Condition removes the reply entirely — it is neither offered nor
shown. A failing Availability Condition still shows the reply, but greyed out and not
selectable. See the Dialogue Editor page for where to set Do Once, Default Reply,
and both conditions on a Reply line.
Building a custom widget¶
A choice UI is made of two widgets:
- The choice widget itself, which must inherit from
ULBRDialogueChoiceWidget. - The reply button widget, which must inherit from
ULBRDialogueChoiceButton.
ULBRDialogueChoiceWidget properties¶
| Property | Purpose |
|---|---|
Dialogue |
The Dialogue asset the choice belongs to. |
DialogueInstance |
The running ALBRDialogueInstance. |
LineIDChoice |
The ID of the Choice line being displayed. |
ButtonClass |
The ULBRDialogueChoiceButton subclass used for the reply buttons. Leave it empty to use the built-in default button, which builds a simple button-with-text on the fly. |
ChoiceTimerHandle |
Timer handle for a timed choice — read it to know how much time is left. |
ChoiceButtons |
The reply buttons currently spawned by this widget. |
Together, Dialogue, DialogueInstance and LineIDChoice give you everything needed to configure
the choice presentation.
Getting the Reply line's own ID from an IDToJump
Several methods return an IDToJump — the ID of the first line played when a reply is
picked, not the ID of the Reply line itself. To recover the Reply line's ID from an
IDToJump, call Dialogue->GetIndex(IDToJump) to get the line index, then
Dialogue->GetParentIndex(that index); the parent index is the Reply line.
ULBRDialogueChoiceWidget methods¶
Methods flagged Overridable are BlueprintNativeEvents — override them in your widget Blueprint
to change behaviour, and call the parent when you want to keep the default. The others are helpers
you call but should not need to reimplement.
| Method | Overridable | What it does | Default behaviour |
|---|---|---|---|
AddChoiceToView |
Yes | Initializes and shows the widget. | Adds the widget to the player screen, switches input to UI only, and shows the mouse cursor. |
AddButtonChoiceToView |
Yes | Called after each reply button is created, to attach it to the choice widget. | Adds the reply button as a child of the choice widget's root panel. |
ChoiceTimedOut |
Yes | Called by the Dialogue Manager when a timed choice runs out. Must call ValidateChoice to confirm the selection. |
Validates the choice line itself, which resolves to GetChoiceDefaultReplyOrFirstID. |
ChoiceSelected |
Yes | Called by a reply button (via ButtonChoiceSelected) when the player picks a reply. Must call ValidateChoice. Use it to run gameplay effects and to distinguish an explicit pick from the timeout default. |
Validates the selected reply. |
ChoiceValidated |
Yes | The final validation step, called once a reply is confirmed. | Calls RemoveWidgetChoice. |
RemoveWidgetChoice |
Yes | Tears the widget down. | Switches input back to game only, hides the mouse cursor, and removes the widget from its parent. |
DisplayChoice |
No | Called by the Dialogue Manager when the choice widget is created. | Sets Dialogue, DialogueInstance and LineIDChoice, then calls AddChoiceButtons. |
AddChoiceButtons |
No | Called by DisplayChoice. |
Iterates the choice's replies and adds a reply button for each available one (buttons whose Availability Condition is false are still added so they can be greyed out). |
GetChoiceDefaultReplyOrFirstID |
No | Returns the IDToJump of the first available Default Reply, or of the first available reply, or -1 if none is available. |
Walks the replies in order: returns the first available reply tagged Default Reply; otherwise the first available reply. |
GetChoiceDefaultReplyID |
No | Returns the IDToJump of the first available Default Reply, or -1. |
Walks the replies in order: returns the first available reply tagged Default Reply. |
Validation is split into two steps
ChoiceValidated is the confirmation hook, and it delegates the actual UI teardown to the
separate, overridable RemoveWidgetChoice. Override RemoveWidgetChoice if you only need to
change how the widget is dismissed (input mode, cursor, animations), and ChoiceValidated if
you need to run logic at the moment a reply is confirmed.
Empty choices do not soft-lock
A choice can end up with no selectable reply (for example, every Do Once reply already
used on a second pass). Libretto detects this: the empty framing is normally skipped upstream,
and if an empty choice widget is still reached, the dialogue is ended cleanly instead of
looping. You do not need to handle this case in your widget.
ULBRDialogueChoiceButton properties¶
| Property | Purpose |
|---|---|
Dialogue |
The Dialogue asset, used to look up the reply this button offers. |
LineID |
The ID of the Reply line (its prompt text). |
LineIDReply |
The reply's IDToJump — the first line played when this reply is selected. |
ButtonChoiceSelected |
The event the choice widget listens to; it is bound to the widget's ChoiceSelected. |
ULBRDialogueChoiceButton methods¶
| Method | Overridable | What it does | Default behaviour |
|---|---|---|---|
ReplySelected |
No | Called when the button is clicked. | Broadcasts ButtonChoiceSelected with IDToJump = LineIDReply. |
GetReplyText |
No | Used to display the button's label. | Returns the localized text of the corresponding Reply line. |
IsReplyAvailable |
No | Used to enable or disable the button. | Returns the reply's availability. |
Not blocking the character (Firewatch-style choices)¶
By default the choice widget takes full control: input goes UI only and the mouse cursor appears, which stops the player from moving. If you want a choice the player can pick while still walking around — the "walk-and-talk" style used in games like Firewatch — follow the pattern implemented in the shipped default widget.
In broad strokes:
- Override
AddChoiceToViewso it does not switch to UI only input and does not block the character. Keep game input active and add the choice UI as an overlay. - Add input bindings to move focus between replies (and to confirm one) instead of relying on mouse clicks.
Flagging a non-blocking choice
A dedicated gameplay tag, Libretto.Flags.Choice.NonBlocking, marks a Choice line as
non-blocking. Put it on the Choice line and map it to your walk-and-talk widget through Widget
Choice Map Class. The C++ base widget does not act on this flag on its own — the non-blocking
behaviour lives in your widget's overrides.