Skip to content

Context and list menus ​

Two menu styles, both client only. Register a menu once under an id, then show it by id.

Context menuList menu
LookTitled panel of rich rows (description, image, progress bar, metadata, badge)Compact keyboard menu in a screen corner
NavigationMouse, with nested menus and a back buttonArrow keys, with scrolling values, checkboxes and paging
CallbacksPer row (onSelect, event, serverEvent)One confirm function for the whole menu, plus onCheck, onSideScroll
Best forInventories, vehicle lists, choices with detailSettings, toggles, option pickers

Neither needs Lua after the menu opens until the player acts: filtering, scrolling and paging all happen in the page.

tm.ui.menu is a third, separate thing: the nested menu of the windows page. Use these two for the standard look, and tm.ui.menu when the menu has to live inside a tm.ui.window page.

Context menus ​

Context menuOpen full size
lua
tm.registerContext({
    {
        id = 'garage_main',
        title = tm.locale.t('garage.title'),
        icon = 'lucide:car',
        options = {
            {
                title = tm.locale.t('garage.myVehicles'),
                description = tm.locale.t('garage.myVehiclesDesc'),
                icon = 'lucide:key-round',
                menu = 'garage_vehicles',                 -- opens another registered menu
            },
            {
                title = tm.locale.t('garage.impound'),
                icon = 'lucide:lock',
                badge = 2,
                serverEvent = 'tm_garages:server:openImpound',
            },
            { title = tm.locale.t('garage.closed'), disabled = true },
        },
    },
    {
        id = 'garage_vehicles',
        title = tm.locale.t('garage.myVehicles'),
        menu = 'garage_main',                             -- parent: shows a back button
        searchable = true,
        options = {
            {
                title = 'Karin Sultan',
                description = tm.locale.t('garage.plate', { plate = 'TM 4821' }),
                progress = 82,                            -- shown as a bar, 0 to 100
                metadata = { 'Engine 82%', 'Fuel 40%' },
                args = { plate = 'TM 4821' },
                onSelect = function(args) spawnVehicle(args.plate) end,
            },
        },
    },
})

tm.showContext('garage_main')
FunctionNotes
tm.registerContext(menu | menu[])Registers one menu or an array of them. Registering an id again replaces it.
tm.showContext(id)Shows a registered menu. Replaces the one on screen without running its onExit. Logs an error for an unknown id.
tm.hideContext(runOnExit?)Closes the open menu. Pass true to run its onExit too.
tm.getOpenContextMenu()The id of the menu on screen, or nil.

A menu is built when it is registered, so register again to change its rows.

FieldMeaning
id, titleRequired.
optionsRows, as an array. A table keyed by name also works: it is sorted by key and the key becomes the title when a row has none.
menuId of the parent menu. Shows a back button that returns to it.
icon, iconColorIcon next to the title.
canCloseEscape and the X close the menu. Default true.
searchable, searchPlaceholderAdds a search box above the rows. It matches title, description and keywords.
positionA corner name or { x, y, right } in pixels.
onExitRuns when the player closes the menu (X or Escape).
onBackRuns when the player presses back.

Row fields ​

FieldMeaning
title, descriptionThe text.
icon, iconColor, iconRight, iconRightColorIcons on the left and right. See icons.
imagePicture beside the row: an item:<name> or an image URL.
progress0 to 100, drawn as a bar. colorScheme colors it.
badgeSmall text or number tag.
arrowShow a chevron. Shown automatically for rows with menu.
metadataDetail lines: { 'a', 'b' }, { Label = 'value' } or { { label = 'Fuel', value = '40%', progress = 40 } }.
keywordsExtra words the search box matches, never shown.
disabledGreyed out and not clickable.
readOnlyNormal looking but not clickable.
menuId of a registered menu to open when chosen.
onSelect(args)Function called when chosen.
event, serverEventClient or server event triggered when chosen, with args.
argsPassed to onSelect and to both events. It never goes to the page.

When a row is chosen, the menu closes before its handlers run, so a handler is free to open a dialog or another menu. Rows with menu keep the interface open and switch to the child.

List menus ​

List menuOpen full size
lua
tm.registerMenu({
    id = 'phone_settings',
    title = tm.locale.t('phone.settings'),
    position = 'top-right',
    pageSize = 6,
    options = {
        { label = tm.locale.t('phone.airplane'), checked = false, args = { key = 'airplane' } },
        { label = tm.locale.t('phone.ringtone'), values = { 'Classic', 'Pulse', 'Silent' }, defaultIndex = 1, args = { key = 'ringtone' } },
        { label = tm.locale.t('phone.storage'), progress = 64, description = tm.locale.t('phone.storageDesc'), close = false },
        { label = tm.locale.t('phone.reset'), icon = 'lucide:rotate-ccw', args = { key = 'reset' } },
    },
    onCheck = function(selected, checked, args) setSetting(args.key, checked) end,
    onSideScroll = function(selected, scrollIndex, args) setSetting(args.key, scrollIndex) end,
    onClose = function(keyPressed) print('closed with', keyPressed) end,
}, function(selected, scrollIndex, args)
    -- a row was confirmed (Enter or click)
    if args.key == 'reset' then resetPhone() end
end)

tm.showMenu('phone_settings')
FunctionNotes
tm.registerMenu(data, confirm?)Registers a menu. confirm(selected, scrollIndex, args) runs when a row is confirmed.
tm.showMenu(id)Shows it.
tm.hideMenu(runOnClose?)Closes the open menu. Pass true to run its onClose (it receives no key).
tm.getOpenMenu()The id of the menu on screen, or nil.
tm.setMenuOptions(id, options, index?)Replaces every row, or just row index when it is given. A menu that is open updates in place and keeps the player's position.

Indexes are 1-based everywhere in Lua (selected, scrollIndex, startItemIndex, defaultIndex, the index of setMenuOptions).

FieldMeaning
id, title, optionsRequired.
position'top-left', 'top-right', 'bottom-left', 'bottom-right' or { x, y, right }.
iconIcon next to the title.
canCloseEscape closes the menu. Default true. Backspace closes it either way.
startItemIndexRow highlighted first.
pageSizeRows per page. Leave out for no paging.
onSelected(selected, scrollIndex, args)The highlight moved to a row.
onSideScroll(selected, scrollIndex, args)A scroll row changed value.
onCheck(selected, checked, args)A checkbox row was toggled.
onClose(keyPressed)The player closed the menu. keyPressed is 'Escape' or 'Backspace'.

Row fields ​

FieldMeaning
labelRequired.
icon, iconColor, iconAnimationIcon, its color and an animation: spin, spinPulse, spinReverse, pulse, beat, fade, beatFade, bounce, shake.
valuesMakes the row scroll left and right through these entries (strings, or { label, description }).
defaultIndexStarting entry of a scroll row.
checkedtrue or false turns the row into a checkbox.
progress, colorSchemeA progress row, 0 to 100.
descriptionShown in the footer while the row is highlighted.
disabledNot selectable.
closefalse keeps the menu open after the row is confirmed.
argsPassed to every callback of this row. It never goes to the page.

The confirmed row's menu closes before confirm runs (unless the row has close = false), so confirm can open another menu.