Lua API
Everything below is tm.ui, loaded on first use and running inside your resource (@tm_lib/init.lua in your manifest). Client only.
tm.ui
| Function | Returns | Notes |
|---|---|---|
tm.ui.window(schema, listeners?) | window handle | Opens a window. Re-using an id replaces that window. listeners is an optional `{ event = fn |
tm.ui.get(id) | handle or nil | Look up an open window. |
tm.ui.isOpen(id?) | boolean | With an id: is that window open. Without: is any open. |
tm.ui.closeAll() | Closes every window from this resource. | |
tm.ui.confirm(opts) | boolean | Yes/no. true only when confirmed. |
tm.ui.alert(opts) | Message with an OK button. Blocks until dismissed. | |
tm.ui.prompt(opts) | values or nil | A form. nil when cancelled. |
tm.ui.input(title, label?, opts?) | string or nil | One text field. |
tm.ui.menu(opts) | window handle | Nested menu. See menus. |
confirm
lua
local ok = tm.ui.confirm({
title = 'Delete vehicle?', text = 'This cannot be undone.',
confirm = 'Delete', cancel = 'Keep', danger = true, -- danger: red tone + red button
})prompt
lua
local values = tm.ui.prompt({
title = 'Transfer', icon = 'lucide:send', submit = 'Send',
fields = {
{ type = 'number', path = 'amount', label = 'Amount', min = 1, rules = { required = true, min = 1 } },
{ type = 'input', path = 'note', label = 'Note' },
},
})Options: title, text (muted line above the fields), icon, fields, values (initial), submit, cancel, width.
input
lua
local name = tm.ui.input('Rename', 'New name', { required = true, placeholder = 'Name', default = 'Old', multiline = false })Window handle
Returned by tm.ui.window and tm.ui.menu.
Listening
| Method | |
|---|---|
win:on(event, fn) | Add a listener. Several per event are allowed. fn(data, eventName). |
win:once(event, fn) | Like on, removed after the first call. |
win:off(event, fn?) | Remove one listener, or all listeners for the event. |
win:onClick(event, fn) | Same as on; reads better for buttons. |
win:onChange(path, fn) | Field changes: fn({ path, value, old }). The field needs notify = true. |
win:onAny(fn) | Every event from this window. |
All of these return the handle, so calls chain. Payloads and event names: events.
Values and nodes
| Method | |
|---|---|
win:set(path, value) / win:set({ [path] = value }) | Change field values. Alias: win:setModel. |
win:get(path) | Last known value (updated by change events and on submit). |
win:getValues() | Copy of all last-known values. |
win:setNode(nodeId, props) | Merge props into the node with that id (text, label, disabled, options, items, ...). |
win:update(schema) | Re-render from a fresh schema, same id and listeners, no flicker. Field values reset to the new schema's. |
win.values, win.id, win.closed | Plain fields. |
Several set / setNode calls in the same tick are merged into one message to the UI.
Lifecycle
| Method | |
|---|---|
win:wait() | Block until submit (returns values) or close (returns nil). |
win:close() | Close from Lua. Does not fire the close event (that is for player dismissal). |
win:setFocus(on) | Give NUI focus back to the game and take it again, e.g. around a progress bar. |
Window options that affect Lua
| Schema field | Effect |
|---|---|
focus = false | Don't take NUI focus (display-only window). Combine with passive = true for ambient overlays (hint cards, HUD panels): they ignore the mouse and Escape. |
keys = true | Forward keydown / keyup events. See events. |
closable = false | Escape and the X do nothing. |
LuaLS annotations
The files under client/ carry ---@class / ---@param annotations, so lua-language-server can autocomplete the handle methods and the menu option fields (add ui/client to your workspace library).