Skip to content

Examples ​

Paste any of these into a client script of a resource that has the ui_page from getting started.

Form with validation and a conditional field ​

Form with validationOpen full size
lua
local values = tm.ui.window({
    title = 'Create character', icon = 'lucide:user-plus',
    children = {
        { type = 'row', children = {
            { type = 'input', path = 'first', label = 'First name', rules = { required = true, minLength = 2 } },
            { type = 'input', path = 'last',  label = 'Last name',  rules = { required = true, minLength = 2 } },
        } },
        { type = 'select', path = 'role', label = 'Role', options = { 'civilian', 'police', 'medic' }, default = 'civilian' },
        { type = 'input', path = 'badge', label = 'Badge', when = { path = 'role', eq = 'police' },
          rules = { required = true, pattern = '^[0-9]{3,5}$', message = '3-5 digits' } },
    },
    footer = {
        { type = 'button', label = 'Cancel', event = 'cancel', close = true, variant = 'ghost' },
        { type = 'button', label = 'Create', event = 'submit', submit = true, close = true, variant = 'primary' },
    },
}):wait()

if not values then return end            -- dismissed
TriggerServerEvent('chars:create', values)   -- validate again on the server

Live settings (events + Lua updating the window) ​

lua
local win = tm.ui.window({
    title = 'Live settings', position = 'right',
    values = { volume = 50 },
    children = {
        { type = 'slider', path = 'volume', label = 'Volume', min = 0, max = 100, notify = true },
        { type = 'text', id = 'status', tone = 'muted', text = 'Move the slider' },
        { type = 'button', label = 'Reset', event = 'reset' },
    },
})

win:onChange('volume', function(d)
    SetVolume(d.value)
    win:setNode('status', { text = ('Volume %s -> %s'):format(d.old, d.value) })
end)
win:onClick('reset', function() win:set('volume', 50) end)

A read-only panel that refreshes (the apiary hive) ​

Build the schema from data, open it once, then :update it whenever the data changes.

lua
local function buildSchema(view)
    return {
        title = 'Beehive', icon = 'lucide:hexagon', tone = 'amber', width = 720,
        children = { { type = 'row', gap = 16, children = {
            { type = 'stack', grow = 1, children = {
                { type = 'meter', label = 'Health', value = view.health },
                { type = 'badge', tone = 'amber', label = view.honey .. ' honey' },
            } },
            { type = 'stack', width = 260, children = {
                { type = 'button', label = 'Feed', event = 'feed', block = true,
                  disabled = view.syrup < 1, disabledWhen = { path = 'busy', truthy = true } },
            } },
        } } },
    }
end

local win = tm.ui.window(buildSchema(view))
win:on('feed', function()
    win:set('busy', true)                 -- buttons with disabledWhen lock immediately
    win:setFocus(false)                   -- give controls back for the progress bar
    if runProgressBar() then
        win:setFocus(true)
        win:update(buildSchema(fetchView()))   -- re-render in place; busy resets with the new schema
    else
        win:setFocus(true)
        win:set('busy', false)
    end
end)
MenuOpen full size
lua
local result = tm.ui.menu({
    title = 'Garage', pageSize = 6,
    options = {
        { label = 'Take out', icon = 'lucide:car', args = 'out' },
        { label = 'Store', icon = 'lucide:parking-square', args = 'store' },
        { label = 'Options', options = {
            { label = 'Auto-lock', checkbox = true, checked = true, onCheck = function(on) SetAutoLock(on) end },
        } },
    },
}):wait()

if result then print(result.args) end

Confirm / alert / prompt / input ​

ConfirmOpen full size
lua
if tm.ui.confirm({ title = 'Delete?', text = 'This cannot be undone.', confirm = 'Delete', danger = true }) then ... end

tm.ui.alert({ title = 'Done', text = 'Vehicle stored.' })

local v = tm.ui.prompt({ title = 'Transfer', fields = {
    { type = 'number', path = 'amount', label = 'Amount', min = 1, rules = { required = true } },
}, submit = 'Send' })

local name = tm.ui.input('Rename', 'New name', { required = true })

Key events ​

lua
local win = tm.ui.window({ title = 'Hotkeys', keys = true, children = { { type = 'text', text = 'Press R' } } })
win:on('keydown', function(d) if d.key == 'r' then print('restart') end end)

Several windows at once ​

lua
tm.ui.window({ id = 'list',   position = 'left',  children = { ... } })
tm.ui.window({ id = 'detail', position = 'right', children = { ... } })
-- Escape closes the topmost closable window. NUI focus is released when the last one closes.

Ambient hint card (the tutorial) ​

A window that never takes focus or the mouse, docked right and slid in:

lua
local function card(step, index, done)
    return {
        id = 'tutorial:' .. step.id,
        eyebrow = 'Getting started', title = step.title,
        icon = done and 'lucide:check' or step.icon, tone = done and 'green' or 'blue',
        width = 320, position = 'right', animation = 'slide-right',
        focus = false, passive = true, closable = false,
        headerNodes = { { type = 'dots', total = 5, current = index, done = done or false } },
        children = {
            { type = 'text', markdown = true, text = 'Hold [[G]] to open the **radial menu**.' },
            { type = 'wrap', children = {
                { type = 'icon', icon = 'lucide:loader-circle', spin = true, size = 14 },
                { type = 'text', tone = 'muted', text = 'Hold the key to try it' },
            } },
        },
    }
end

local win = tm.ui.window(card(step, 2))     -- slides in
win:update(card(step, 2, true))               -- same id: turns into the "done" state in place
win:close()                                   -- slides out

A new id for each step gives the slide between steps; :update keeps the same card. See [player]/tutorial/modules/client.lua.

Dashboard layout ​

lua
tm.ui.window({
    title = 'Profile', width = 640, position = 'top-left',
    children = {
        { type = 'avatar', name = 'Jane Doe', subtitle = 'Los Santos PD', badge = 'On duty', badgeTone = 'green' },
        { type = 'grid', columns = 3, gap = 10, children = {
            { type = 'stat', label = 'Arrests', value = '42', tone = 'blue' },
            { type = 'stat', label = 'Fines', value = '$8,120', tone = 'green' },
            { type = 'stat', label = 'Reports', value = '17', tone = 'amber' },
            { type = 'chart', kind = 'line', label = 'Shifts this week', data = { 3, 5, 2, 8, 6, 9, 4 }, span = 2 },
            { type = 'chart', kind = 'bar', data = { { label = 'M', value = 3 }, { label = 'T', value = 5 } }, labels = true, tone = 'green' },
        } },
    },
})