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.
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 dismissedMenu options
| Field | |
|---|---|
id, title, icon, width, position | As a window. Default width 380. |
options | The list (see below). |
pageSize | Items per page (default 8). Paging appears when there are more. |
closeOnSelect | Close after choosing an item. Default true. |
closable | Escape 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 | |
|---|---|
label | Text. |
description | Small second line. |
icon | Iconify id, item:<name> or URL. |
caption | Small text on the right. |
checkbox, checked | Renders a checkbox. Checkboxes never close the menu. |
disabled | Greyed out and skipped by the keyboard. |
close | Override closeOnSelect for this item. |
options | Makes the item a submenu (a chevron is shown). |
args | Any 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 / Down | Move the highlight (skips disabled items, wraps). |
| Left / Right, PageUp / PageDown | Previous / next page. |
| Enter | Choose. Enters a submenu, toggles a checkbox. |
| Backspace | Back out of a submenu. |
| Escape | Back 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:
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
menu:setOptions(newOptions) -- replaces the list and returns the page to the top levelCheckbox 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.