Skip to content

Lumo UI — API ​

All exports are client side unless marked server. Call them as exports['lumo-ui']:Name(...). Scripts written for qb-target, qb-menu, qb-input, qb-radialmenu or progressbar can keep their old calls through the [compat] shims (see Migration).

Notifications ​

lua
exports['lumo-ui']:Notify(text, type, duration, icon, title)
-- type: 'info' (or 'primary') | 'success' | 'error' | 'warning'
-- text may also be a table: { text = '...', caption = 'Title' }
TriggerEvent('lumo-ui:client:notify', text, type, duration)       -- same, as an event
TriggerClientEvent('lumo-ui:client:notify', src, text, type, duration) -- from the server

lumo-hud-ui:client:notify is still accepted for older scripts.

Text UI ​

lua
exports['lumo-ui']:ShowText('[E] Open door')         -- '[E]' becomes the key cap
exports['lumo-ui']:ShowText('Open door', 'right', 'G') -- position: left (default) | right; players can override it in /ui
exports['lumo-ui']:ChangeText('[E] Close door')
exports['lumo-ui']:HideText()
-- DrawText is an alias of ShowText. KeyPressed() flashes the key cap.

exports['lumo-ui']:DisplayTextUI('Open', 'E')        -- the text-UI module's own prompt
exports['lumo-ui']:HideTextUI()
exports['lumo-ui']:IsTextUIShown()

-- World labels pinned to a point, drawn only when near and on screen:
exports['lumo-ui']:Create3DTextUI('bin', { coords = vec3(...), displayDist = 6.0, interactDist = 2.0,
    enableKeyClick = true, keyNum = 38, key = 'E', text = 'Search bin',
    triggerData = { triggerName = 'myres:searchBin', args = {} } })
exports['lumo-ui']:Update3DTextUI('bin', { text = 'Empty' })
exports['lumo-ui']:Delete3DTextUI('bin')
-- Create3DTextUIOnPlayers(id, { id = serverId, ... }) / Delete3DTextUIOnPlayers(id): above another player.

Target eye ​

Same names and arguments as qb-target, so code written for it moves over by changing 'qb-target' to 'lumo-ui' (or by keeping the shim).

lua
exports['lumo-ui']:AddBoxZone(name, center, length, width, { heading, minZ, maxZ }, targetOptions)
exports['lumo-ui']:AddCircleZone(name, center, radius, { useZ }, targetOptions)
exports['lumo-ui']:AddPolyZone(name, points, { minZ, maxZ }, targetOptions)
exports['lumo-ui']:AddComboZone(zones, { name }, targetOptions)
exports['lumo-ui']:AddEntityZone(name, entity, { length, width }, targetOptions)
exports['lumo-ui']:RemoveZone(name)

exports['lumo-ui']:AddTargetModel(models, targetOptions)   -- RemoveTargetModel(models, labels)
exports['lumo-ui']:AddTargetEntity(entities, targetOptions) -- RemoveTargetEntity(entities, labels)
exports['lumo-ui']:AddTargetBone(bones, targetOptions)     -- RemoveTargetBone(bones, labels)
exports['lumo-ui']:AddGlobalPed / AddGlobalVehicle / AddGlobalObject / AddGlobalPlayer(targetOptions)
exports['lumo-ui']:RemoveGlobalPed / ... (labels)

-- targetOptions:
{
    distance = 2.5,
    options = {
        {
            label = 'Search', icon = 'fas fa-magnifying-glass',
            type = 'client',           -- client | server | command | qbcommand
            event = 'myres:search',    -- or: action = function(entity) ... end
            job = 'police',            -- or { police = 2 } for a minimum grade; also gang, jobType, citizenid, item
            excludejob = 'ambulance',
            canInteract = function(entity, distance, data) return true end,
        },
    },
}

Options are kept by label: adding a label again replaces it, removing by label removes it. SpawnPed, RemoveSpawnedPed, DeletePeds, GetPeds, UpdatePedsData, AllowTargeting(bool), IsTargetActive(), IsTargetSuccess() and the Get*Data readers work as in qb-target.

Zones need no PolyZone: box, circle, polygon, entity and combo zones are tested inside lumo-ui. /targetdebug (admins) adds test options to everything.

Radial menu ​

lua
local id = exports['lumo-ui']:AddRadialOption({
    id = 'mdt', title = 'MDT', icon = 'tablet',
    type = 'command',          -- client | server | command | qbcommand
    event = 'mdt',             -- or action = function() ... end
    shouldClose = true,
    canOpen = function() return true end, -- hide the item when false
    downed = false,            -- true: the only kind of item shown while downed
    -- items = { ... }         -- a submenu instead of an event
}, optionalId)
exports['lumo-ui']:RemoveRadialOption(id)
exports['lumo-ui']:IsRadialOpen()
exports['lumo-ui']:CloseRadial()

Items added by a resource are removed when that resource stops. Items come in this order: config.lua Items, the job's JobItems (as a "Work" submenu while on duty), added items sorted by id, then the vehicle menu while seated.

Events: lumo-ui:client:radialOpened, lumo-ui:client:radialClosed.

Context menu ​

lua
exports['lumo-ui']:OpenMenu({
    { header = 'Shop', isMenuHeader = true },
    { header = 'Water', txt = '$5', icon = 'fas fa-bottle-water', item = 'water',
      params = { event = 'shop:server:buy', isServer = true, args = { item = 'water' } } },
    { header = 'Close', action = function() end },
})
exports['lumo-ui']:CloseMenu()
exports['lumo-ui']:ShowHeader(entries)   -- header-only, focused with Left Alt
exports['lumo-ui']:IsMenuOpen()

params takes isServer, isCommand, isQBCommand or isAction; otherwise event is a client event. item shows the inventory image. Function fields never reach the browser. Event: lumo-ui:client:menuClosed.

Input dialog ​

lua
local data = exports['lumo-ui']:ShowInput({
    header = 'Fine', submitText = 'Send',
    inputs = {
        { type = 'number', name = 'amount', text = 'Amount', isRequired = true },
        { type = 'select', name = 'reason', text = 'Reason', options = { { value = 'speed', text = 'Speeding' } } },
        -- text | password | number | radio | checkbox | select | color | date | time
    },
})
if data then print(data.amount, data.reason) end   -- nil when closed
exports['lumo-ui']:CloseInput()

ShowInput waits for the answer, so call it from a thread.

Progress bar ​

lua
exports['lumo-ui']:Progress({
    name = 'repair', duration = 5000, label = 'Repairing', useWhileDead = false, canCancel = true,
    controlDisables = { disableMovement = true, disableCarMovement = true, disableMouse = false, disableCombat = true },
    animation = { animDict = 'mini@repair', anim = 'fixing_a_ped' },
    prop = { model = 'prop_tool_wrench', bone = 57005, coords = vec3(0.1, 0, 0), rotation = vec3(0, 0, 0) },
}, function(cancelled) end)
-- ProgressWithStartEvent / ProgressWithTickEvent / ProgressWithStartAndTick as in progressbar
exports['lumo-ui']:CancelProgress()
exports['lumo-ui']:IsDoingSomething()

Every start gets one answer: cancelled = false when it completes, true when it is cancelled or refused because another bar is running.

HUD ​

lua
exports['lumo-ui']:SetStatus('hunger', 80)   -- hunger | thirst | stress, 0–100 (standalone/custom frameworks)
exports['lumo-ui']:GetStatus('stress')
exports['lumo-ui']:GetMapShape()              -- 'square' | 'circle'

QBCore/Qbox status comes from player metadata and hud:client:UpdateNeeds; ESX from esx_status:onTick. The hud:client:* and seatbelt:client:* events from qb-hud are handled (ToggleSeatbelt, UpdateNitrous, UpdateHarness, OnMoneyChange, ShowAccounts, …).

Server:

lua
exports['lumo-ui']:AddStress(src, amount)
exports['lumo-ui']:RemoveStress(src, amount)
exports['lumo-ui']:GetStress(src)
exports['lumo-ui']:GetAdminSettings()
TriggerServerEvent('hud:server:GainStress', amount)    -- clamped
TriggerServerEvent('hud:server:RelieveStress', amount)

Overlays ​

lua
-- Timers (several at once, no focus). Ids are per calling resource.
local id = exports['lumo-ui']:StartTimer({ id = 'delivery', seconds = 90, mode = 'countdown',
    label = 'DELIVERY WINDOW', text = 'Reach the customer', position = 1, format = 'clock', urgent = 10000 })
exports['lumo-ui']:UpdateTimer(id, { text = 'Destination changed', position = 3 })
exports['lumo-ui']:HideTimer(id)            -- StopTimer is an alias; IsTimerActive(id)
-- position: 1 centre-bottom, 2 centre-top, 3 right, 4 above location,
-- or { x = 72, y = 18, anchor = 'center' }. Finished: 'lumo-hud-ui:client:timerFinished' (id, owner)

-- Minigame bar (you drive the progress)
exports['lumo-ui']:ShowMinigame({ kind = 'shake', label = 'STRUGGLE', hint = 'SHAKE MOUSE', progress = 0 })
exports['lumo-ui']:UpdateMinigame(progress, outcome)  -- outcome: nil | 'win' | 'fail'
exports['lumo-ui']:HideMinigame()

-- Repeat-tap minigame (lumo-ui drives it)
exports['lumo-ui']:StartTapMinigame({ difficulty = 'normal', label = 'Keep tapping' }, function(won, info) end)
exports['lumo-ui']:CancelTapMinigame()      -- difficulty: normal | medium | hard

-- Key hints and noise meter
exports['lumo-ui']:ShowKeys({ { key = 'E', label = 'Pick up' }, { key = 'G', label = 'Drop' } })
exports['lumo-ui']:HideKeys()
exports['lumo-ui']:ShowNoise({ label = 'NOISE', level = 20, danger = 70 })
exports['lumo-ui']:UpdateNoise(level, sneaking)
exports['lumo-ui']:HideNoise()

Settings and other events ​

EventSideMeaning
lumo-hud-ui:client:settingsChanged (settings)client, fireda player changed /ui
lumo-hud-ui:client:requestSettingsclient, listenask for a settingsChanged
lumo-hud-ui:client:phoneState (open)client, listenphone open: HUD makes room
lumo-hud-ui:client:startLayoutEditclient, listenopen the layout editor
lumo-ui:client:playerData (data)client, firedPlayerData refreshed
lumo-ui:client:openMenu / closeMenu / progress / cancelProgressclient, listenevent forms of the exports

Other UIs (inventory, phone…) can share the theme by including '@lumo-ui/client/bridge.lua' in their client scripts.