Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

textfield

A single-line text input: a search box, a launcher query, a password. The engine holds what the user types (the draft); Lua sees it only through callbacks and cannot set it. Focus, editing keys, the draft’s lifetime and password fields are on input.

A launcher: the field filters a list as the user types, the arrow keys move a selection, Enter launches.

local apps = { "Firefox", "Files", "Terminal", "Text Editor", "Settings" }
local query = state("query", "")
local selected = state("selected", 1)

local matches = query:map(function(q)
    local out = {}
    for _, name in ipairs(apps) do
        if name:lower():find((q or ""):lower(), 1, true) then out[#out + 1] = name end
    end
    return out
end)

local launcher = column { width = 320, padding = 12, spacing = 8, background = "#1E1E2E", radius = 12, children = {
    rect { width = "Fill", padding = { left = 10, right = 10 }, radius = 8, background = "#313244", children = {
        textfield {
            width = "Fill",
            height = 36,
            font_size = 14,
            foreground = "#CDD6F4",
            placeholder = "Search…",
            autofocus = true,
            on_change = function(text) query:set(text); selected:set(1) end,
            on_navigate = function(key)
                if key == "down" then selected:set(math.min(#matches:get(), selected:get() + 1))
                elseif key == "up" then selected:set(math.max(1, selected:get() - 1)) end
            end,
            on_submit = function() print("launch", matches:get()[selected:get()]) end,
        },
    } },
    list {
        width = "Fill",
        source = matches,
        key = function(name) return name end,
        itemfn = function(name)
            return rect {
                width = "Fill", padding = { left = 10, right = 10, top = 6, bottom = 6 }, radius = 8,
                background = selected:map(function(index)
                    return matches:get()[index] == name and "#45475A" or "#00000000"
                end),
                children = { text { content = name, foreground = "#CDD6F4" } },
            }
        end,
    },
} }

return { panel { id = "launcher", layer = "Top", anchor = { top = true },
    keyboard_interactivity = "OnDemand", child = launcher } }

The panel needs keyboard_interactivity for the field to get keys (panel).

Properties

textfield takes the common properties, plus:

PropertyTypeDefaultBehaviour
placeholderstring|Bound""Shown while the field is empty, focused or not. Never submitted
font_sizenumber|Bound, [1, 8192]12Size of the text and placeholder
foregroundColor|Bound"#FFFFFF"Colour of the text and placeholder
text_align"Start"|"Center"|"End"|Bound"Start"Aligns the text inside the field’s box
autofocusboolean|BoundfalsePlain fields only: take the keyboard, empty, when the surface gets it or the field appears, calling on_change(""). The first in document order wins; never steals from a field already typing or one a press just left
on_changefun(text: string)NoneFull text after every edit
on_submitfun(text: string)NoneEnter with the full text; the field stays focused and clears. Never fires on a secure_submit field
on_cancelfun(cleared: boolean)NoneEscape; cleared says whether it removed text. A plain field clears (firing on_change("") only if there was text), gives up focus, then calls this. A secure_submit field scrubs and stays armed. Without it Escape clears and keeps focus
on_navigatefun(key: "up"|"down"|"left"|"right"|"page_up"|"page_down"|"tab"|"backtab")NoneKeys a single-line field does not use, for moving a list selection; repeats while held. "left"/"right" only when the caret cannot move that way and Shift is up
secure_submit{ capability: string, action: string }|BoundNoneMakes the field masked; keys never reach Lua. Both non-empty UTF-8 strings: only lock/authenticate, polkit/authenticate and network/connect; any other pair or key is an error (secure fields)
mask_characterstring|Bound"•"Drawn per typed character in a secure_submit field. Only the first character counts; "" hides the length

The field has no intrinsic size: give it width and height. It draws one line of text and a caret, vertically centred, in the fonts chain; there is no font property. It reads wl_keyboard, not an input method, so there is no CJK composition and no dead keys.

A field with none of on_change, on_submit and secure_submit never takes focus. The draft follows the node, so give the field a stable id when siblings before it come and go (identity).

How do I…

TaskAnswer
Filter a list as the user typesThe example above: on_change sets a state, the list’s source maps it
Move a selection with the arrow keyson_navigate, as above; pair it with scroll(name):reveal to keep the row in view (input)
Focus the field when a panel opensautofocus = true and a panel with keyboard_interactivity
Close on a second Escapeon_cancel(cleared): close only when cleared is false
Ask for a passwordsecure_submit = { capability = "lock", action = "authenticate" } (secure fields)
Submit a password from a buttonA button with submit = true
Style the box around the fieldWrap it in a rect with background, radius and border_*; the field draws only text and caret
Debounce a searchsignals

Gotchas

TrapFix
The field does not appearIt has no intrinsic size. Give width and height
Typing does nothingThe surface needs keyboard focus (keyboard_interactivity on a panel), and the field needs on_change, on_submit or secure_submit
on_cancel or on_navigate alone never firesNeither makes the field focusable. Add on_change or on_submit
You cannot set or clear the draft from LuaThe draft is the engine’s. Enter and Escape clear it; removing the node drops it
on_submit never fires on a password fieldA secure_submit field sends to its capability instead
font on a textfield is refusedFields use the fonts chain
background on a textfield is refusedIt is not a box. Wrap it in a rect

See also: input, list, text, surfaces: panel.

Source: vocabulary, content parsers, secure_submit, plain fields, focus.