en-ui renders the interface pieces every resource needs: notifications, a progress bar, an input dialog, an interaction prompt and an NPC conversation screen. They are drawn on en-ui's own page, so every resource gets the same look without building anything. You call them through the library.
Add en-ui to your resource's dependencies if you use these.
At a glance
| Side | Function | Blocks | Without en-ui |
|---|---|---|---|
| server | encore.notify(target, data) | no | Sent anyway; clients without en-ui show nothing |
| client | encore.notify(data) | no | Printed to the F8 console |
| client | encore.progress(data) | yes | Returns false |
| client | encore.cancelProgress() | no | Does nothing |
| client | encore.isProgressActive() | no | Returns false |
| client | encore.inputDialog(title, fields, options?) | yes | Returns nil |
| client | encore.showPrompt(data) | no | Does nothing |
| client | encore.hidePrompt() | no | Does nothing |
| client | encore.dialog.show(data) | yes | Returns nil |
| client | encore.dialog.close() | no | Does nothing |
| client | encore.dialog.isOpen() | no | Returns false |
When a client call finds en-ui isn't running, it logs en-ui is not running; interface calls will do nothing once.
Blocking calls yield, so run them from a thread, event handler, target onSelect or radial onSelect.
Notifications
A notification appears on the left of the screen, above the vitals, and disappears after its duration.
-- client
encore.notify({ title = 'Radio', description = 'Signal lost', type = 'warning' })
encore.notify('Saved') -- a plain string becomes { description = 'Saved' }
-- server
encore.notify(source, { description = 'You found 3 scrap', type = 'success' })
encore.notify(-1, { title = 'Broadcast', description = 'Horde inbound at Sandy Shores', type = 'error' })On the server, target is a player id, or -1 for everyone. A target of 0 or nil prints the message to the server console, which is handy for commands run from the console.
Options
| Field | Type | Default | Behaviour |
|---|---|---|---|
title | string? | none | Heading line |
description | string? | none | Body text |
type | string | 'inform' | 'inform', 'success', 'warning' or 'error'. 'info' works as an alias for 'inform'. Unknown values become 'inform' |
duration | number | 5000 | Milliseconds, clamped between 1500 and 15000 |
icon | string? | by type | An icon name. Defaults: inform uses info, success uses check, warning and error use alert. Unknown names show the alert glyph |
- At most five notifications are visible. The oldest leaves first.
errornotifications are announced withrole="alert", the others withrole="status".- Text is inserted as text, never HTML, so showing a player-authored string is safe.
Progress bar
encore.progress(data) shows a bar at the lower centre of the screen, runs an optional animation, and blocks until the bar finishes or is cancelled. It returns true only if the bar completed.
| Field | Type | Default | Behaviour |
|---|---|---|---|
label | string | '' | Text above the bar |
duration | number | 3000 | Milliseconds, minimum 100 |
canCancel | boolean | true | Players cancel with X. A keycap hint is shown. Set false to prevent cancelling |
disable | table | none | Controls disabled every frame while the bar runs (below) |
anim | table | none | An animation or scenario to play (below) |
maxDistance | number? | none | Cancels if the player moves further than this many metres from where they started |
useWhileDead | boolean? | false | Unless set, the bar cancels when the player dies or ragdolls |
disable keys:
| Key | Disables |
|---|---|
move | Walking, sprinting, jumping and stealth |
car | Steering, accelerating, braking and leaving a vehicle |
combat | Firing, attacking, aiming and melee |
mouse | Looking around |
anim takes one of two shapes:
{ dict, clip, flag?, blendIn?, blendOut? }plays an animation.flagdefaults to49, blend speeds to3.0. If the dictionary doesn't exist or doesn't load within 3 seconds, the bar runs without it.{ scenario, playEnter? }starts a scenario in place.playEnterdefaults totrue.
The player's tasks are cleared when the bar ends, if an animation started.
Other behaviour:
- Only one bar runs at a time. Calling
encore.progresswhile one is active returnsfalseimmediately. - The bar shows the time remaining, then "Done" or "Stopped".
encore.cancelProgress()cancels the active bar, and itsencore.progresscall returnsfalse.encore.isProgressActive()tells you whether a bar is running.
local done = encore.progress({
label = 'Bandaging',
duration = 4000,
disable = { move = true, combat = true },
anim = { dict = 'missheistdockssetup1clipboard@idle_a', clip = 'idle_a' },
maxDistance = 2.0,
})
if done then
TriggerServerEvent('my-medical:bandaged')
endA progress bar runs on the player's client. The server can't tell whether it really finished. Before rewarding, check on the server what you can: that the player has the item, is in the right place, and isn't calling the event faster than the bar allows.
Input dialog
encore.inputDialog(title, fields, options?) opens a modal form and blocks until the player submits or cancels. It returns the values in field order, or nil if they cancelled.
Options
| Field | Type | Default | Meaning |
|---|---|---|---|
description | string? | none | Text under the title |
submitLabel | string? | 'Confirm' | Label of the submit button |
allowCancel | boolean? | true | Whether the player can cancel (including with Escape) |
Fields
Every field accepts type, label, required, description, default and placeholder, plus the keys for its type:
type | Control | Extra keys | Value returned |
|---|---|---|---|
'input' | Text box. Also used for unknown types | password = true masks the text; min and max are character counts | string |
'number' | Number box | min, max, step | number, or nothing if left empty |
'select' | Drop-down | options = { { value, label? }, ... }; placeholder defaults to 'Choose…' | The chosen option's value, as a string |
'date' | Date picker | min, max | 'YYYY-MM-DD' |
'textarea' | Three-row text area | min and max are character counts | string |
'checkbox' | Toggle switch | default is a boolean | boolean |
Validation
Fields are checked in place, with plain-language errors, before the form submits:
required: "This field is required."- Numbers: "Enter a number.", "Must be at least N.", "Must be at most N."
- Dates: "Pick a date."
- Text:
minandmaxapply to the length of the trimmed value.
Behaviour
- The dialog takes NUI focus while it is open, and Tab stays inside it.
- Only one dialog can be open. A second call returns
nilstraight away. - If en-ui stops while a dialog is open, the call returns
nil. - To close an open dialog from code, call
exports['en-ui']:CloseInputDialog(). The waiting call returnsnil. - An empty optional number comes back as nothing, so read values by position (
values[2]) rather than relying on the length of the list.
local values = encore.inputDialog('Name your base', {
{ type = 'input', label = 'Name', required = true, min = 3, max = 24 },
{ type = 'select', label = 'Access', options = {
{ value = 'party', label = 'Party members' },
{ value = 'private', label = 'Only me' },
}, default = 'party' },
{ type = 'checkbox', label = 'Show on map', default = true },
}, { submitLabel = 'Claim' })
if not values then return end
local name, access, showBlip = values[1], values[2], values[3]
TriggerServerEvent('my-bases:claim', name, access, showBlip)Dialog validation runs in the player's NUI and can be bypassed. When you send the values to the server, validate them again there: types, lengths, allowed option values and permissions.
-- server
RegisterNetEvent('my-bases:claim', function(name, access, showBlip)
local src = source
if type(name) ~= 'string' then return end
name = encore.string.trim(name)
if #name < 3 or #name > 24 then return end
if access ~= 'party' and access ~= 'private' then return end
showBlip = showBlip == true
-- ...
end)Interaction prompt
A prompt is a keycap and a label at the lower centre of the screen. It stays until you hide it.
| Field | Type | Default | Meaning |
|---|---|---|---|
key | string | 'E' | Text in the keycap |
label | string | '' | What pressing the key does |
subject | string? | none | Smaller secondary text, such as the place or object |
encore.showPrompt({ key = 'E', label = 'Open locker', subject = 'Police Station' })
-- later
encore.hidePrompt()The prompt and the progress bar share a spot on screen. While a bar is running it takes the spot, and the prompt returns when the bar ends.
Prompts are also the fallback interaction when en-target isn't running. See Targeting interface.
NPC conversations
encore.dialog is the conversation screen every survivor, trader and dealer uses. The camera frames the character you are talking to, their lines type in one page at a time, and the player picks a reply. Each encore.dialog.show call is one step of the conversation.
local choice = encore.dialog.show(data) -- the chosen choice id, or nil if they left
encore.dialog.close()
local open = encore.dialog.isOpen()Fields
| Field | Type | Default | Meaning |
|---|---|---|---|
speaker | string | '' | Name of who is talking (up to 60 characters) |
title | string? | none | Secondary heading, such as a place (up to 60 characters) |
lines | string or string[] | required | What they say. Each line is a page (up to 800 characters each) |
choices | table? | none | Replies (below). With no choices, the conversation closes after the last line |
details | table? | none | Label and value rows shown with the choices, such as a reward |
ped | number? | none | The ped to frame. The camera moves to their face and the player turns toward them |
camera | boolean? | true | false keeps the normal camera, and releases a framing camera left from an earlier step |
hold | boolean? | false | Keep the screen and camera up after a choice, ready for your next step |
allowLeave | boolean? | true | Show the Leave control |
Each choice is { id, label, icon?, disabled?, reason?, leave? }:
| Key | Meaning |
|---|---|
id | Returned when picked. Defaults to the choice's position as a string |
label | Button text (up to 120 characters) |
icon | An icon name. Unknown names are not shown |
disabled | Shown but can't be picked |
reason | Why a disabled choice is locked, for example 'Needs level 5' |
leave | Marks the choice as the way out and styles it that way. Picking it still returns its id |
Each detail is { label, value, tone? }, where tone is 'accent' or 'danger'.
Behaviour
- Choices are numbered with keycaps and appear once the last line has finished typing.
showreturnsnilif the player leaves, or reaches the end of a step that has no choices. The screen closes.- Without
hold, the screen and camera close as soon as a choice is made. Withhold = truethey stay up. If you don't show another step within 5 seconds, they close by themselves so the player is never stuck. - Only one step can be waiting at a time. A second
showwhile one is waiting returnsnil. - The conversation closes automatically when the player dies or their character unloads.
Example
local ped = data.entity
local choice = encore.dialog.show({
ped = ped,
speaker = 'Sgt. Rhodes',
title = 'Burton Checkpoint',
lines = { 'You look like you can carry things.', 'I have work, if you want it.' },
details = { { label = 'Reward', value = '$150 · 200 XP', tone = 'accent' } },
choices = {
{ id = 'accept', label = "I'll do it", icon = 'check' },
{ id = 'trade', label = "Let's trade", icon = 'cash' },
{ id = 'hard', label = 'The hard job', disabled = true, reason = 'Needs level 5' },
{ id = 'leave', label = 'Not now', leave = true },
},
hold = true,
})
if choice == 'accept' then
encore.dialog.show({
ped = ped,
speaker = 'Sgt. Rhodes',
lines = 'Bring me three medkits from the hospital. Don’t die.',
})
TriggerServerEvent('my-quests:accept', 'rhodes_medkits')
else
encore.dialog.close()
if choice == 'trade' then openTrader('rhodes') end
end