Progress
A timed action. Blocking: it returns true if the player saw it through and false if it was cancelled or interrupted, so call it from a thread.
Beyond the visual it also handles the animation, attached props, disabled controls and the interruption rules for the duration of the action.

progressBar
local completed = exports.of_ui:progressBar(data)progressCircle
local completed = exports.of_ui:progressCircle(data)
The same options, drawn as a circle. position and percent apply only here.
| Field | Type | Description |
|---|---|---|
duration | number | Milliseconds. Required. |
label | string? | Text on the bar. |
position | string? | middle or bottom. Circle only. |
percent | boolean? | Show the percentage inside the circle. Circle only. |
canCancel | boolean? | Lets the player cancel with the cancel key. |
anim | table? | Animation to play. See below. |
prop | table? | Prop(s) to attach. See below. |
disable | table? | { move?, sprint?, car?, combat?, mouse? }: controls suppressed for the duration. |
useWhileDead | boolean? | Keep going if the player dies. |
allowRagdoll | boolean? | Keep going while ragdolled. |
allowCuffed | boolean? | Keep going while cuffed. |
allowFalling | boolean? | Keep going while falling. |
allowSwimming | boolean? | Keep going while swimming. |
Returns true when it ran to completion, false when cancelled or interrupted, nil when it never started (the player was already in a state the options do not allow).
CreateThread(function()
local completed = exports.of_ui:progressBar({
label = 'Repairing engine',
duration = 8000,
canCancel = true,
disable = { move = true, combat = true },
anim = { dict = 'mini@repair', clip = 'fixing_a_ped' },
prop = { model = 'prop_tool_wrench', bone = 28422,
pos = vec3(0.0, 0.0, 0.0), rot = vec3(0.0, 0.0, 0.0) },
})
if not completed then
exports.of_ui:notify({ type = 'error', title = 'Repair cancelled' })
return
end
TriggerServerEvent('vehicle:repaired')
end)Interruption
By default the action stops if the player dies, ragdolls, gets cuffed, starts falling or starts swimming. Each allow* flag opts out of one of those checks.
The same checks run before the bar appears: a call that would immediately be interrupted returns nil without showing anything.
anim
| Field | Type | Description |
|---|---|---|
dict | string? | Animation dictionary. |
clip | string? | Clip inside the dictionary. |
scenario | string? | Scenario name, instead of dict/clip. |
playEnter | boolean? | Play the scenario's enter animation. Default true. |
blendIn / blendOut | number? | Blend speeds. Default 3.0 / 1.0. |
duration | number? | Animation length. Defaults to the action's. |
flag | number? | Animation flags. Default 49. |
playbackRate | number? | Playback speed. |
lockX / lockY / lockZ | boolean? | Lock movement on that axis. |
The animation stops when the action ends, whether it completed or not.
prop
One prop, or an array of them:
| Field | Type | Description |
|---|---|---|
model | string | Object model. |
bone | number? | Ped bone to attach to. Default 60309 (right hand). |
pos | vector3 | Offset from the bone. |
rot | vector3 | Rotation. |
rotOrder | number? | Rotation order. |
Props are replicated to nearby players, so everyone sees the wrench, not just the person holding it. They are deleted when the action ends or the player disconnects.
Models larger than 2.5 units on any axis are rejected, as are peds and vehicles. The limit on simultaneous props comes from the ox:progressPropLimit convar (default 2).
cancelProgress
exports.of_ui:cancelProgress()Cancels the running action from code. The waiting call returns false.
progressActive
local active = exports.of_ui:progressActive()Whether an action is running.
Notes
- Calls queue. A second
progressBarwaits for the first to finish rather than replacing it. canCanceluses a keybind the player can rebind,Xby default, listed in the FiveM keybind settings.invBusyis set for the duration and restored after, so inventory scripts treat the player as occupied.animandpropneedox_lib: it is what loads models and animation dictionaries. The bar itself works without it.
