Skip to content

Menus ​

tm.ui.menu opens a navigable menu: nested submenus, checkboxes, paging and keyboard control. Everything except the final choice happens in the page. Opening a submenu, turning a page or moving the highlight never talks to Lua.

lua
local menu = tm.ui.menu({
    title = 'Main menu',
    icon = 'lucide:menu',
    pageSize = 6,
    options = {
        { label = 'Change outfit', icon = 'lucide:shirt', description = 'Open the wardrobe', args = { action = 'outfit' } },
        { label = 'Settings', icon = 'lucide:settings', options = {
            { label = 'Fullscreen', checkbox = true, onCheck = function(checked) print(checked) end },
            { label = 'Volume', caption = '80%', func = function() openVolume() end },
        } },
        { label = 'Locked', disabled = true },
    },
})

local result = menu:wait()          -- { key = '1', option = <your table>, args = ... } or nil when dismissed
Field
id, title, icon, width, positionAs a window. Default width 380.
optionsThe list (see below).
pageSizeItems per page (default 8). Paging appears when there are more.
closeOnSelectClose after choosing an item. Default true.
closableEscape and the X. Default true.
onSelect(option, key)Called for every chosen item.
onCheck(checked, option, key)Called for every checkbox toggle.

Option fields ​

Field
labelText.
descriptionSmall second line.
iconIconify id, item:<name> or URL.
captionSmall text on the right.
checkbox, checkedRenders a checkbox. Checkboxes never close the menu.
disabledGreyed out and skipped by the keyboard.
closeOverride closeOnSelect for this item.
optionsMakes the item a submenu (a chevron is shown).
argsAny data you want back. Stays in Lua, it is never sent to the page.
func(option, key)Called when the item is chosen.
onCheck(checked, option, key)Called when the checkbox is toggled.

Functions and args are kept in Lua. The page only receives labels and a key like "2.1" (second item, first child), and reports that key back.

Keyboard and mouse ​

Key
Up / DownMove the highlight (skips disabled items, wraps).
Left / Right, PageUp / PageDownPrevious / next page.
EnterChoose. Enters a submenu, toggles a checkbox.
BackspaceBack out of a submenu.
EscapeBack out of a submenu first; closes the menu at the top level.

Hover highlights; click chooses.

Listening ​

tm.ui.menu returns the normal window handle, so events work too:

lua
menu:on('select', function(data) print(data.key) end)
menu:on('check',  function(data) print(data.key, data.checked) end)
menu:on('close',  function() print('dismissed') end)

Updating options ​

lua
menu:setOptions(newOptions)   -- replaces the list and returns the page to the top level

Checkbox state lives in the page. setOptions resets it from each option's checked.

:wait() ​

Resolves with { key, option, args } for the first item chosen, or nil when the menu is dismissed. Checkbox toggles don't resolve it. For menus that stay open (closeOnSelect = false), prefer callbacks over :wait().

As a raw node ​

The helper builds this for you; you can also embed a menu in any window (for example beside a form) with { type = 'menu', id = 'menu', pageSize = 6, title = 'Optional heading', items = {...} }, where each item is { key, label, description, icon, caption, checkbox, checked, disabled, close, options }. You then handle select / check events yourself and look items up by key.