Interface overview
tm_lib ships its own interface: toasts, text prompts, dialogs, menus, a radial menu, progress bars and skill checks. It is the replacement for ox_lib's UI in every Twilight Moon resource (resources never call ox_lib's UI directly), and it is fully themed from the in game admin panel.
There are two interface pages in tm_lib. Do not mix them up:
| Page | Folder | What it is | Docs |
|---|---|---|---|
| Interface (this section) | ui/ | The fixed set of ready made widgets below. tm_lib owns the page, you call functions. | this section |
| Windows | ui-windows/ | A schema driven page for custom forms, panels and tabs: you describe a window as a Lua table. Your resource points its own ui_page at it. | tm.ui.window |
Use the interface functions for the common things (tell the player something, ask a yes or no, pick from a list, show a progress bar). Reach for tm.ui.window when you need a screen of your own.
Setup
Nothing beyond the usual two lines. There is no ui_page to set and nothing to build:
-- fxmanifest.lua
dependencies { 'ox_lib', 'tm_lib' }
shared_scripts { '@ox_lib/init.lua', '@tm_lib/init.lua' }tm_lib must be started before your resource (ensure tm_lib first). Every function below is then a field of tm.
What is available
| Feature | Functions | Side | Page |
|---|---|---|---|
| Toasts | tm.notify, tm.dismissNotification, tm.clearAllNotifications | client and server | Notifications |
| Text prompts | tm.showTextUI, tm.updateTextUI, tm.hideTextUI, tm.isTextUIOpen | client | Notifications |
| Confirm dialog | tm.alertDialog, tm.closeAlertDialog | client | Dialogs |
| Form dialog | tm.inputDialog, tm.closeInputDialog | client | Dialogs |
| Context menus | tm.registerContext, tm.showContext, tm.hideContext, tm.getOpenContextMenu | client | Menus |
| List menus | tm.registerMenu, tm.showMenu, tm.hideMenu, tm.getOpenMenu, tm.setMenuOptions | client | Menus |
| Radial menu | tm.radial.addItem, tm.radial.removeItem, tm.radial.registerMenu, tm.radial.open, tm.radial.close, tm.radial.disable and more | client | Radial menu |
| Progress | tm.progressBar, tm.progressCircle, tm.progressActive, tm.cancelProgress | client | Progress |
| Skill check | tm.skillCheck, tm.skillCheckActive, tm.cancelSkillCheck | client | Skill checks |
| Theme | tm.theme.tokens, tm.theme.push, tm.theme.bind | client | Theme |
| Admin hub | tm.admin.registerPanel, tm.admin.openFor | server | Settings and the admin panel |
The names match ox_lib's UI functions on purpose, so moving a script over is mostly replacing lib. with tm..
Rules that apply to all of it
- Client only, except
tm.notify.tm.notify(source, data)also exists on the server. Everything else needs the client. - Blocking calls wait for the player.
alertDialog,inputDialog,progressBar,progressCircleandskillCheckpause the calling thread until the player answers (they use a promise internally), so call them from a thread, an event handler or a callback, never from the main chunk of a file. - One at a time. There is one alert dialog, one input dialog, one context menu, one list menu, one progress and one skill check on screen. Opening another dialog or menu replaces the current one and its waiting caller gets the cancel result (
'cancel'ornil). Starting a progress or skill check while another runs returnsfalseimmediately and logs a warning. Toasts and text prompts stack. - Cleanup is automatic. When your resource stops, everything it opened is closed, so a player is never left with a focused dialog nothing will answer. When
tm_libstops, everything closes. - Focus is shared. The interface tracks NUI focus across all open widgets, so closing one never steals focus from another that is still open.
- A bad callback cannot break the interface. Your
onSelect,onCloseand similar callbacks run protected. An error is logged (tm.log.error) and the interface carries on. - Text is a locale key. Pass
tm.locale.t('your.key')as the text of anything the player reads, never a literal.
Icons
Wherever a function takes an icon, it accepts:
| Form | Example |
|---|---|
| Lucide (bundled, always works) | 'lucide:car' |
| Font Awesome class names | 'fas fa-coins', 'fa-solid fa-car', or the bare name 'car' |
| ox_lib style tuple | { 'fas', 'star' } |
| Inventory item image | 'item:water' |
| Image URL | 'https://...' |
Prefer lucide: names. Font Awesome is bundled too, so none of these are fetched online.
Colors
type, color, iconColor and colorScheme take a tone name, a scheme name or any CSS color.
| Kind | Values |
|---|---|
| Tones | success (alias positive), error (negative), warning, info (inform, ongoing), primary, accent, dark |
| Schemes | red, orange, yellow, green, teal, blue, indigo, purple, pink, gray, white |
| Anything else | passed through as a CSS color: '#ff8800', 'rgb(255 136 0)' |
Screen positions
Toasts and text prompts use nine zones: top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right. The short names top, bottom, left, right, left-center and right-center also work. Menus use top-left, top-right, bottom-left, bottom-right, or a table { x = 40, y = 120 } / { right = 40, y = 120 } in pixels.
Autocomplete
The interface functions are typed in tm_lib/types (see Lua types), so with the language server set up you get hover docs and parameter hints for TmNotifyData, TmAlertData, TmInputRow, TmContextMenu, TmMenuData, TmProgressData and the rest.
Next
- Notifications: toasts and text prompts
- Dialogs: confirm and form dialogs
- Menus: context menus and list menus
- Radial menu: the wheel, and which system draws it
- Progress: bars and circles
- Skill checks
- Theme: restyling the interface