Overview
In UEFN, the add-widget-to-screen workflow lets you programmatically attach any widget (text blocks, images, buttons, canvas panels, etc.) to a specific player's UI layer. Unlike static HUD elements placed in the editor, widgets added through Verse can appear, update, and disappear in response to live game state — a player picks up a key, a banner flashes; a round ends, the scoreboard slides in.
The core object is player_ui, which you obtain from a player using GetPlayerUI[Player] (from /Fortnite.com/UI). Once you have a player_ui reference you can:
AddWidget(Widget)— attach a widget with default slot settings.AddWidget(Widget, Slot)— attach a widget with aplayer_ui_slotthat controls z-order and input mode.RemoveWidget(Widget)— cleanly detach the widget when it is no longer needed.
Widgets themselves expose SetVisibility, SetEnabled, and GetVisibility so you can show/hide without a full remove-and-re-add cycle.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
Scenario: Objective Banner on Trigger
A player steps on a pressure plate. A bold text banner reading "Capture the Vault!" appears on their screen. Ten seconds later the banner fades away automatically.
This example wires a trigger_device to display a text_block widget for the triggering player, then removes it after a delay.
using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/Colors }
# Localisation helper — Verse requires message-typed text for UI labels.
ObjectiveBannerText<localizes>(S : string) : message = "{S}"
objective_banner_device := class(creative_device):
# Wire this to a Trigger device placed in the level.
@editable
Plate : trigger_device = trigger_device{}
# How long (seconds) the banner stays on screen.
@editable
DisplayDuration : float = 10.0
# Called when the island starts.
OnBegin<override>()<suspends> : void =
# Subscribe so every time the plate fires we run OnPlateTriggered.
Plate.TriggeredEvent.Subscribe(OnPlateTriggered)
# Handler — receives an optional agent from the trigger.
OnPlateTriggered(MaybeAgent : ?agent) : void =
if (A := MaybeAgent?):
# Cast agent → player so we can get the player_ui.
if (P := player[A]):
spawn { ShowBannerForPlayer(P) }
# Async task: build the widget, show it, wait, remove it.
ShowBannerForPlayer(P : player)<suspends> : void =
# Obtain the player's UI container.
if (UI := GetPlayerUI[P]):
# Build a text block with our objective message.
Banner := text_block:
DefaultText := ObjectiveBannerText("Capture the Vault!")
DefaultTextColor := NamedColors.Yellow
# Configure the slot: render on top of game HUD, no input capture.
Slot := player_ui_slot:
ZOrder := 10
InputMode := ui_input_mode.None
# Add the widget to this player's screen.
UI.AddWidget(Banner, Slot)
# Keep the banner visible for the configured duration.
Sleep(DisplayDuration)
# Clean up — remove the widget when the timer expires.
UI.RemoveWidget(Banner)```
**Line-by-line explanation**
| Lines | What's happening |
|---|---|
| `Plate.TriggeredEvent.Subscribe(OnPlateTriggered)` | Registers our handler with the trigger's event. |
| `if (A := MaybeAgent?)` | Safely unwraps the `?agent` the event delivers. |
| `if (P := player[A])` | Downcasts `agent` to `player`; fails gracefully for NPCs. |
| `spawn { ShowBannerForPlayer(P) }` | Launches the async banner task without blocking the event handler. |
| `GetPlayerUI[P]` | Returns the `player_ui` for this player; failable — wrapped in `if`. |
| `text_block{ DefaultText := ... }` | Constructs the widget inline with a localised message. |
| `player_ui_slot{ ZOrder := 10, InputMode := ui_input_mode.None }` | Puts the banner above the default HUD layer and doesn't steal input. |
| `UI.AddWidget(Banner, Slot)` | Attaches the widget to the player's screen. |
| `Sleep(DisplayDuration)` | Suspends the coroutine for 10 seconds (non-blocking). |
| `UI.RemoveWidget(Banner)` | Detaches and destroys the widget from the player's screen. |
---
## Common patterns
### Pattern 1 — Toggle visibility without removing
Sometimes you want to hide a widget temporarily (e.g. during a cutscene) without destroying it. Use `SetVisibility` on the widget itself.
```verse
using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
HudToggleText<localizes>(S : string) : message = "{S}"
hud_toggle_device := class(creative_device):
@editable
ToggleButton : button_device = button_device{}
# We store the widget so the handler can reference it.
var MaybeHudWidget : ?text_block = false
OnBegin<override>()<suspends> : void =
ToggleButton.InteractedWithEvent.Subscribe(OnButtonPressed)
OnButtonPressed(MaybeAgent : ?agent) : void =
if (A := MaybeAgent?, P := player[A], UI := GetPlayerUI[P]):
if (Existing := MaybeHudWidget?):
# Widget already exists — toggle its visibility.
CurrentVis := Existing.GetVisibility()
if (CurrentVis = widget_visibility.Visible):
Existing.SetVisibility(widget_visibility.Hidden)
else:
Existing.SetVisibility(widget_visibility.Visible)
else:
# First press — create and add the widget.
Label := text_block:
DefaultText := HudToggleText("Press again to hide me!")
UI.AddWidget(Label)
set MaybeHudWidget = option{Label}
Key idea: SetVisibility(widget_visibility.Hidden) hides the widget but keeps it in the player_ui so you can show it again instantly without re-adding.
Pattern 2 — Show a widget to ALL players at round start
Broadcast a "Round Start" banner to every player simultaneously by iterating GetPlayspace().GetPlayers().
using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
RoundStartText<localizes>(S : string) : message = "{S}"
round_start_broadcast_device := class(creative_device):
@editable
StartTrigger : trigger_device = trigger_device{}
OnBegin<override>()<suspends> : void =
StartTrigger.TriggeredEvent.Subscribe(OnRoundStart)
OnRoundStart(MaybeAgent : ?agent) : void =
spawn { BroadcastToAll() }
BroadcastToAll()<suspends> : void =
AllPlayers := GetPlayspace().GetPlayers()
for (P : AllPlayers):
spawn { ShowRoundBanner(P) }
ShowRoundBanner(P : player)<suspends> : void =
if (UI := GetPlayerUI[P]):
Banner := text_block:
DefaultText := RoundStartText("Round 1 — GO!")
Slot := player_ui_slot:
ZOrder := 5
InputMode := ui_input_mode.None
UI.AddWidget(Banner, Slot)
Sleep(3.0)
UI.RemoveWidget(Banner)
Key idea: Each player gets their own widget instance. Sharing a single widget object across players is not supported — always construct a fresh widget per player.
Pattern 3 — Disable interaction on a widget
If you add a button widget but want to temporarily prevent the player from clicking it (e.g. during a cooldown), use SetEnabled.
using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
CooldownLabelText<localizes>(S : string) : message = "{S}"
cooldown_button_device := class(creative_device):
@editable
ActivateTrigger : trigger_device = trigger_device{}
CooldownSeconds : float = 5.0
OnBegin<override>()<suspends> : void =
ActivateTrigger.TriggeredEvent.Subscribe(OnActivate)
OnActivate(MaybeAgent : ?agent) : void =
if (A := MaybeAgent?, P := player[A]):
spawn { ShowCooldownUI(P) }
ShowCooldownUI(P : player)<suspends> : void =
if (UI := GetPlayerUI[P]):
# Build a button-style text block to represent the interactive element.
ActionLabel := text_block:
DefaultText := CooldownLabelText("Ability (cooling down...)")
UI.AddWidget(ActionLabel)
# Disable interaction during cooldown.
ActionLabel.SetEnabled(false)
Sleep(CooldownSeconds)
# Re-enable after cooldown expires.
ActionLabel.SetEnabled(true)
Sleep(30.0)
UI.RemoveWidget(ActionLabel)
Key idea: SetEnabled(false) greys out the widget and blocks player interaction without hiding it — ideal for cooldown UI.
Gotchas
1. GetPlayerUI is failable — always wrap in if
GetPlayerUI[P] uses the <decides> effect, meaning it can fail (e.g. if the player has left the island). Always call it inside an if expression:
if (UI := GetPlayerUI[P]):
UI.AddWidget(MyWidget)
Calling it bare without if is a compile error.
2. Widget text must be message, not string
The DefaultText field of text_block (and similar widgets) is typed message, not string. You cannot pass a raw string literal. Declare a localisation helper at file scope:
MyLabel<localizes>(S : string) : message = "{S}"
# Then use:
DefaultText := MyLabel("Hello, player!")
There is no StringToMessage function — the <localizes> annotation is the only bridge.
3. One widget instance per player
A single widget object cannot be shared between multiple player_ui instances. If you call UI.AddWidget(SharedWidget) for two different players, the second call may silently fail or produce unexpected results. Always construct a new widget object for each player.
4. Always RemoveWidget when you're done
Widgets added with AddWidget persist until explicitly removed with RemoveWidget or until the player leaves. Forgetting to remove them causes stale UI elements to linger for the entire session. Use Sleep + RemoveWidget in a <suspends> coroutine to guarantee cleanup.
5. spawn is required to call <suspends> functions from event handlers
Event handler methods (subscribed via .Subscribe(...)) are not suspending contexts. If your widget logic uses Sleep, wrap the call in spawn { ... }:
OnTriggered(MaybeAgent : ?agent) : void =
if (A := MaybeAgent?, P := player[A]):
spawn { ShowAndRemoveWidget(P) } # correct
# ShowAndRemoveWidget(P) # compile error — handler can't suspend
6. ZOrder controls layering — use it deliberately
The default AddWidget(Widget) (no slot) places the widget at z-order 0. If your widget appears behind other HUD elements, pass an explicit player_ui_slot with a higher ZOrder. Values above 10 typically render above most built-in Fortnite HUD layers.