DatumHuePlatform v0.0.24datumhue.com
Contents

handle54 methods, 33 fields

UiElement

A node in the UI tree, returned by the constructor methods on a parent element (ui.root:panel{...}, panel:button{...}, ...) and by source:mount(opts) on a render target. Methods adjust layout, content, hit-testing, and appearance.

Obtained from
datumhue.events.on (in its callback), datumhue.wm.wrap_app, Scene:mount, Image:mount, DrawCanvas:mount, Book:mount, Book:element, datumhue.ui.root and 1 more
Accepted by
datumhue.app.spawn, Scene:mount, Image:mount, DrawCanvas:mount, Book:mount

The examples on this page start from:

lua
local panel = datumhue.ui.root:panel({
    flex_direction = "column",
    padding = 16,
    gap = 8,
})

Fields 33

FieldTypeAccessDescription
visiblebooleanread/writeWhether the element and its children are shown. On a touch control, the value you set: a touch_only control is also hidden while input.last_source is not touch.
displayui.Displayread/writeLayout display mode. Assigning none removes the element from layout while keeping its subtree (and scroll position) alive; other values restore it. A text input lays its text and children out in a flex row, and assigning it a block or grid display raises.
posVec2?read/writeLeft/top anchor in pixels as a vec2; nil when unset. Assign a vec2.
sizeVec2?read/writeWidth/height in pixels as a vec2; nil when unset. Assign a vec2.
boundsui.Bounds?read-onlyThe element's laid-out border box in UI px, relative to the top-left of the calling app's content area (the window for the root app); scrolling a container above it moves it. Read-only. nil before the element's first layout and while it or an ancestor has display = "none". Layout runs after the app's frame, so a change made now reads back from the next frame.
colorColor | ThemedColorread/writeBackground color. Assign a datumhue.theme.token(...) to follow the theme, or a Color to pin it; reading gives the resolved Color shown. A button keeps an assigned fill through hover and press, which draw its label color (on a card button, the theme's button text color) over it at 8% and 10%, and reading it then gives that layered fill; while disabled it shows the theme's disabled fill. A button's border takes its fill's color too, unless the button was given a border_color.
textstring?read/writeText content of a label, text input, button or link, and the source a code element shows; nil on other elements, including card buttons. A label with max_lines reads its whole text, and one with marquee on the text it loops. Assigning relabels a button or link in place, keeping its focus, hover and pressed state, and stops following any locale; assigning to a text input replaces its value silently (no on_change); assigning to a code element shows the new source, colored as before. Raises on a card button.
accessible_labelstring | Message | nilread/writeThe name assistive technology announces for a button, image, text input or radio group, in place of a button's text. Reads the resolved name, or nil when none was set; nil on other elements. A message follows the active locale. Assigning keeps focus and state: a text input's text and selection, and a radio group's selection; assigning nil clears the name, so a text button is named by its text again. Raises on other elements, when nil is assigned to a card button, which needs a name, and when a name is assigned to an image mounted with decorative = true, which reads nil.
marqueebooleanread/writeWhether an overflowing label loops its text horizontally, pausing briefly at each wrap. Text that fits stays still. While on, the label lays its text out on one line, breaking no word: as wide as that line where there is room, and in a narrower row or column as narrow as the room it gets, with no smallest width, looping what does not fit. The looping line is clipped at the label's sides. Writable on labels; false elsewhere, and raises on assignment to a non-label, and on turning it on for a label with max_lines.
tintColor | ThemedColor | nilread/writeAn image's tint: a color each texel is multiplied by, alpha included, so white leaves the image as it is and a lower alpha fades it. Assign a datumhue.theme.token(...) to follow the theme, as color does, or a Color to pin it; nil takes it off. Reads the color shown in the current theme, or nil when the image has none. nil on other elements; assigning to one raises.
max_linesinteger?read/writeThe most lines a label shows; nil for no limit. A longer text ends in "..." on the last line, cut at a glyph. While clamped, a word wider than the label breaks at a glyph, so a single long word is cut too and the label can shrink to one glyph wide in a row; nil puts the label's own overflow_wrap back. A clamped label narrower than "..." shows as much of it as fits, clipped at its box. The label is as wide as its whole text would be and as tall as the lines it shows, and the cut follows its width; text still reads the whole text. nil on other elements. Assigning raises below 1, on a label with marquee on, and on any other element.
overflow_wrapui.OverflowWrap?read/writeHow a label breaks a word wider than its box; "break_word" by default. Either way the label's smallest width in a row is its widest word, so it is narrower than that word only when its box is: given a width or a max_width such as "100%", or stretched across a narrower column. While max_lines is set the clamp breaks such a word at a glyph, and a label with marquee on keeps its text on one line; the label breaks as this says again once they are off. nil on other elements. Assigning raises on other elements, and on nil.
placeholderstring?read/writeA text input's placeholder, shown while it is empty; nil when it has none. Assigning replaces it in place, keeping focus and the typed value, and nil removes it. nil on other elements; assigning to one raises.
leading_iconImage?read/writeA text input's leading icon: the image drawn inside its border at its start, one text line tall and level with its first line, as a mask in the theme's secondary text color (its alpha is the shape; its own colors are not drawn). The field's start padding makes room for it, so the text and the placeholder start after it, and the field grows no taller. A press on it focuses the field with the caret at the end; it is no focus stop and has no accessible name. Assigning replaces it and nil takes it off; removing the image takes it off too. nil on other elements. Assigning raises on other elements, and an image on a field with line numbers or whose start padding is a percentage.
clearablebooleanread/writeWhether a text input shows a clear button at its end while it holds text and is enabled. A press on it empties the field as deleting the text would, so on_change fires with "", and leaves focus in the field; it is no focus stop, and its accessible name is the datumhue-text-input-clear message. While it shows, the field's end padding makes room for it: at least 24 UI px wide (44 at touch density), as tall as the field inside its border, which grows no taller. false on other elements. Assigning raises on other elements, and true on a multi-line input or one whose end padding is a percentage.
scrollVec2?read/writeScroll offset in UI px (scroll containers only; nil otherwise). Assign a vec2; clamped to the scrollable bounds. Raises on a non-scroll element.
scroll_maxVec2?read-onlyMaximum scroll offset (content minus viewport, UI px; zero until first layout). nil for non-scroll elements.
enabledbooleanread/writeWhether an interactive widget (a button, checkbox) accepts input. Setting false grays it out and suppresses its click / keyboard activation; true restores it. Reads true on non-interactive elements.
checkedbooleanread/writeWhether a checkbox is checked. Assigning updates it silently (the on_change callback fires only on user toggles, not programmatic writes). Reads false on non-checkbox elements; assigning to one raises.
valuenumber | Color | nilread/writeA value-bearing widget's current value: a number for a slider or number input, an integer instant (Unix microseconds) for a date/time picker, or a Color for a color picker. Assigning updates it silently (no on_change). nil on elements that hold no value; assigning to one raises.
Example
local volume = panel:slider({ min = 0, max = 10, step = 1 })
volume.value = 7
print(volume.value) --> 7.0

local tint = panel:color_picker({})
tint.value = datumhue.color("#e67e80")

local due = panel:date_picker({})
due.value = datumhue.time.from_calendar({ year = 2026, month = 10 })
print(due.value) --> 1790812800000000
minnumber?read/writeA slider's range minimum. Assigning re-clamps value into the new range. nil on non-slider elements; assigning to one raises.
maxnumber?read/writeA slider's range maximum. Assigning re-clamps value into the new range. nil on non-slider elements; assigning to one raises.
stepnumber?read/writeA slider's keyboard / track-click step increment. nil on non-slider elements; assigning to one raises.
selectedinteger | integer[] | nilread/writeCurrent selection as a 1-based index. A radio group or single-select list or table reads an integer (or nil); a multi-select list or table reads an index array. A data table's indices are display positions, and its selection follows the rows across a sort. Assigning selects silently (no on_change): an integer (or nil to clear) for radio/single, an array for multi. nil on elements that aren't a radio group, list, or table; an out-of-range index, or an array on a radio group, raises.
Example
local fruit = panel:list({
    items = { "Apple", "Pear", "Plum" },
    multi = true,
})
fruit.selected = { 1, 3 }
print(#fruit.selected, fruit.selected[2]) --> 2 3

local size = panel:radio_group({ options = { "S", "M", "L" } })
size.selected = 2
print(size.selected) --> 2
pressedbooleanread-onlyWhether a pointer (the mouse or a touch) holds a press on the element; on a touch control, whether a touch holds it. Read-only; false on non-interactive elements.
hoveredbooleanread-onlyWhether a pointer (the mouse or a touch) is over the element while none holds a press on it. Read-only; false while it is pressed and on non-interactive elements.
focusedbooleanread-onlyWhether this element itself holds input focus, so keys and pad activation go to it. false while its app is not the input owner (another app is focused, or a workstation dialog is open); the element the app last focused gets focus back when the app is focused again. Read-only.
default_focusbooleanread/writeWhether an arrow key, D-pad or left stick press with nothing focused in this element's navigation scope (the app, or the popover or menu it is in) lands here, while it shows and is enabled: ahead of an autofocus text input and of the top-left widget. Tab and Shift+Tab with nothing focused start here too, ahead of the first (last) widget in tab order. Where several have it, the last one set wins. Default false. It never takes focus by itself. Reads false on an element that cannot take focus; assigning to one raises.
strikethroughbooleanread/writeWhether a line is drawn through a text label. Assigning toggles it. Reads false on non-text elements; assigning to one raises.
underlinebooleanread/writeWhether a line is drawn under a text label. Assigning toggles it. Reads false on non-text elements; assigning to one raises.
stickVec2?read-onlyA touch stick's deflection: y up, length at most 1, zero at rest. Read-only. nil on other elements.
deflectionnumber?read-onlyA touch stick's distance from the base's center, in units of radius. It is not clamped, so a value past 1 means the thumb is beyond the travel; 0 at rest. Read-only. nil on other elements.
line_marksui.LineMark[]read/writeMarks on lines of a multi-line text input or a code element. Each draws a band in its kind's color behind every row of its 1-based line, wrapped rows included, under the text and scrolling with it. With line_numbers, the sign cell after the numbers shows its sign in its kind's color, so a mark never rests on color alone; without them a mark is the band only. Neither moves the text, and marks are not announced: say the line in words too. Reading gives a new array in line order, {} when there are none and on any other element. Assigning replaces every mark, and {} clears them. On a text input a mark keeps to its line as the user types, pastes, cuts, undoes and redoes: a mark below the edit moves by the lines it adds or removes, and a mark on a line it changes or removes goes. Assigning text clears them. Assigning raises, changing nothing, on a single-line text input or any other element, for a line below 1 or past the last, and for two marks on one line.
Example
local answer = panel:code({
    text = "local total = 0\nfor i = 1, 3 do\n    total = total + i\nend",
    syntax = "lua",
    line_numbers = true,
    width = 320,
})
answer.line_marks = {
    { line = 3, kind = "error" },
    { line = 1, kind = "info" },
}
print(answer.line_marks[1].line, answer.line_marks[1].kind) --> 1 info

Methods 54

UiElement:remove

method
UiElement:remove()

Remove the element and its children. Raises on ui.root (the app's own content area) — remove its children or individual elements instead.

UiElement:reorder

method
UiElement:reorder(children: UiElement[])

Move the listed children of this element to the front, in the given order; the other children follow in their current order. The elements are kept, with their focus, hover, scroll, handlers and state; layout, navigation by the arrow keys and the Tab order follow from the next frame. A call that leaves the order as it is changes nothing. Raises when an entry is not a child of this element or repeats an earlier entry, before moving any.

Example
local first = panel:label({ text = "First" })
local second = panel:label({ text = "Second" })
panel:reorder({ second, first })

UiElement:router

method
UiElement:router(opts?: ui.RouterOptions) -> Router

Create an in-app navigation region under this element and return its Router. The region fills this element; declare routes with router:route and navigate with show/push/pop/go. Pages are retained (hidden) when backgrounded, so going back restores their scroll and state.

Example
local router = panel:router({ pop_on_cancel = true })
print(router.depth) --> 0

opts ui.RouterOptions options 1 key

KeyTypeDefaultDescription
pop_on_cancelbooleanfalseGo back a page, as router:pop() does, on each cancel request (Escape, gamepad B, Back) that reaches the router while it has a page above its root; when false, the router never takes one.

UiElement:option

method
UiElement:option(index: integer) -> UiElement

Returns the i-th radio option (1-based) of a radio group as a UiElement. Raises on a non-group element or an out-of-range index.

Example
local size = panel:radio_group({ options = { "S", "M", "L" } })
local large = size:option(3)
large:on_hover(function()
    print("L")
end)

UiElement:item

method
UiElement:item(index: integer) -> UiElement

Returns the i-th row (1-based) of a list as a UiElement. Raises on a non-list element or an out-of-range index.

Example
local fruit = panel:list({ items = { "Apple", "Pear", "Plum" } })
local pear = fruit:item(2)
pear:on_press(function()
    print("Pear")
end)

UiElement:on_click

method
UiElement:on_click(callback?: function)

Set, replace, or clear (pass nil) the click handler at runtime. Fires only on an interactive element (a button).

Example
local save = panel:button({ text = "Save" })
save:on_click(function()
    print("saved")
end)
-- nil takes the handler off again.
save:on_click(nil)

UiElement:on_pick

method
UiElement:on_pick(callback?: function)

Set, replace, or clear (pass nil) an open_button's pick handler at runtime — the path to bind a <open_button> declared in DHML from the hosting app. Called with the picked File / File[] / Dir on success (never on cancel); with none bound, a click opens no dialog.

Example
local open = panel:open_button({ text = "Open", mode = "files" })
open:on_pick(function(files)
    for _, file in ipairs(files) do
        print(file.name)
    end
end)

UiElement:on_save

method
UiElement:on_save(callback?: function)

Set, replace, or clear (pass nil) a save_button's save handler at runtime. Called with the read-only File handle after the bytes are committed (never on cancel).

Example
local export = panel:save_button({ text = "Export", source = "1,2\n" })
export:on_save(function(file)
    print("saved " .. file.name)
end)

UiElement:on_save_source

method
UiElement:on_save_source(source?: string | Bytes)

Set the bytes a save_button writes at its next click, captured now. Pass nil to clear (a click then does nothing). The way to keep a save button's payload current — call it whenever the app's data changes. Raises on a non-widget element.

Example
local export = panel:save_button({ text = "Export" })
export:on_save_source("a,b\n1,2\n")
export:on_save_source(datumhue.bytes("a,b\n3,4\n"))

UiElement:on_copy_source

method
UiElement:on_copy_source(source?: string | Bytes)

Set the text a copy_button places on the clipboard at its next click, captured now — a string or UTF-8 Bytes (non-UTF-8 raises). Pass nil to clear (a click then does nothing). The way to keep a copy button's payload current — call it whenever the app's data changes. Raises on a non-widget element.

Example
local copy = panel:copy_button({ label = "Copy" })
copy:on_copy_source("#e67e80")
copy:on_copy_source(datumhue.bytes("#a7c080"))

UiElement:row

method
UiElement:row(index: integer) -> table

The i-th row (1-based, display order — the sorted order while sorted) of a data table as a {column = value} table over its configured columns, holding the data's own values rather than the text the cells show; a cell missing from the data reads nil. Raises on a non-table element or an out-of-range index.

Example
local scores = datumhue.bytes("name,score\nAda,3\nLin,5\n"):csv()
local tbl = panel:table({
    data = scores,
    columns = { { column = "name" }, { column = "score" } },
})
local row = tbl:row(2)
print(row.name, row.score) --> Lin 5

UiElement:on_hover

method
UiElement:on_hover(callback?: function)

Set, replace, or clear (pass nil) the hover-enter handler at runtime. Fires when a pointer (the mouse or a touch) enters this element or any of its descendants.

Example
local card = panel:panel({ padding = 8 })
card:on_hover(function()
    card.color = datumhue.theme.token("surface_raised")
end)

UiElement:on_hover_exit

method
UiElement:on_hover_exit(callback?: function)

Set, replace, or clear (pass nil) the hover-exit handler at runtime. Fires when the last pointer leaves this element and all of its descendants; a press held inside it keeps it entered until the press ends.

Example
local card = panel:panel({ padding = 8 })
card:on_hover_exit(function()
    card.color = datumhue.theme.token("panel_background")
end)

UiElement:on_press

method
UiElement:on_press(callback?: function)

Set, replace, or clear (pass nil) the press handler (fires while held) at runtime. Fires only on an interactive element. Raises on a card button's content, whose presses are the card's.

Example
local key = panel:button({ text = "Hold" })
key:on_press(function()
    key.text = "Held"
end)

UiElement:on_release

method
UiElement:on_release(callback?: function)

Set, replace, or clear (pass nil) the release handler at runtime. Fires only on an interactive element. Raises on a card button's content, whose presses are the card's.

Example
local fire = panel:button({ text = "Fire" })
fire:on_release(function()
    print("released")
end)

UiElement:on_double_click

method
UiElement:on_double_click(callback?: function)

Set, replace, or clear (pass nil) the double-click handler at runtime. Fires on a second press within the double-click window. Fires only on an interactive element (a panel or button). Raises on a card button's content, whose presses are the card's.

Example
local tile = panel:panel({ width = 64, height = 64 })
tile:on_double_click(function()
    print("opened")
end)

UiElement:on_focus

method
UiElement:on_focus(callback?: fun(e: ui.FocusEvent))

Set, replace, or clear (pass nil) the focus handler. It fires when keyboard focus enters this element's subtree: the element, or an element inside it (a popover or menu hung from it or from an element inside it included), gains focus while focus is outside it. A move between two elements inside the subtree fires nothing, as with on_hover, and focus on a part of a composite widget is focus on the widget (see ui.focused), so a move between its parts fires nothing anywhere. It fires on any element, focusable or not, whatever moved focus: a press, Tab, focus navigation, focus(), focus moving off a removed or hidden element, a closing popover giving focus back, or your app losing focus and getting it back. Changes within one frame count as one move, to where focus ended up. For each move your on_blur handlers run before your on_focus handlers, each innermost element first. An on_blur follows once focus leaves, unless the element is removed first. Setting a handler on another app's element raises.

UiElement:on_blur

method
UiElement:on_blur(callback?: fun(e: ui.FocusEvent))

Set, replace, or clear (pass nil) the blur handler. It fires when keyboard focus leaves this element's subtree, for an element outside it or for none, and never for a move between elements inside it. It pairs with on_focus and follows its rules for what is inside the subtree, which focus changes count and the order handlers run in. An element removed while focus is inside it hears no on_blur: its handlers go with it. Setting a handler on another app's element raises.

UiElement:on_cancel

method
UiElement:on_cancel(callback: (fun(e: ui.CancelEvent))?)

Set, replace, or clear (pass nil) the element's cancel handler. While one is set, the element consumes every cancel request (Escape, gamepad B, Back) that reaches it: one made while focus is on it or inside it, or on datumhue.ui.root one made with nothing focused. Setting a handler on another app's element raises; a window manager sets one on the frame wm.wrap_app returned to take the requests the wrapped app leaves, and those any app living in its window leaves.

UiElement:on_resize

method
UiElement:on_resize(callback: (fun(size: Vec2?))?)

Set, replace, or clear (pass nil) the resize handler. It fires once after the element's first layout following the call (in the next frame when it is already laid out), then whenever either side of its laid-out border box changes by 0.5 UI px or more: at most once per frame, with the latest size, one frame after the layout, never for a move alone. size is the border-box size in UI px, nil when the element leaves layout (it or an ancestor gets display = "none"), and the size again when it returns, so it always agrees with bounds. On datumhue.ui.root it fires on window resize, Display size (zoom) change, window-manager bounds or inset changes, and snaps. Setting a handler on another app's element raises.

UiElement:on_visibility

method
UiElement:on_visibility(callback?: fun(visible: boolean))

Set, replace, or clear (pass nil) the handler for changes to this element's own visible (a parent's does not count). It runs in the frame after the change, whatever made it: your own write, a light dismiss or cancel request closing a popover, or the workstation showing a window manager's frame when the framed app's init ends. Changes within one frame coalesce, and it runs only with a value that differs from the one it last reported, or from the element's value when the handler was first set; replacing the handler keeps that value. Setting a handler on another app's element raises.

UiElement:on_drag

method
UiElement:on_drag(callback: (fun(e: ui.DragEvent))?)

Set, replace, or clear (pass nil) the drag handler, for the mouse and touch alike. A primary press (the left mouse button or a touch contact) on the element or a descendant, including a button or checkbox inside it, arms a drag; the nearest element with a handler takes it once the pointer has moved more than 4 UI px (mouse) or 8 (touch) along either axis. A press on a slider, scrollbar, text input or color picker inside the element keeps its own drag, and a press in a popover or menu anchored inside it is the overlay's. For a touch, a scroll container inside the element takes the movement instead when it runs along an axis the container scrolls. From its start the drag owns the pointer: nothing under it clicks or activates, and it follows the pointer outside the element and the window until the release, a touch cancel, window focus loss, a workstation dialog opening over it, or the element being removed or hidden (visible = false, or display = "none" on it or an ancestor). Only the first touch contact drives a drag, and keyboard focus plays no part: a press that moves focus to another app keeps its drag. A replaced handler hears the rest of the drag; clearing the handler cancels it. Setting a handler on another app's element raises, and so does setting one on a card button's content, whose presses are the card's.

UiElement:on_context

method
UiElement:on_context(callback: (fun(e: ui.ContextEvent))?)

Set, replace, or clear (pass nil) the context handler: the element's actions are asked for, as the right-click menu or an item's Actions. It fires on a right-click (the right mouse button released over the element), a touch held 500 ms within 8 UI px of where it landed (that touch then clicks nothing, and neither scrolls nor drags), the Menu key or Shift+F10 while the element has keyboard focus, and gamepad X (the west face button) while it has focus and ui.focus_navigation acts. A request goes to the nearest element with a handler, walking up from the element under the pointer or the focused one within its app, and only that handler hears it; a request in a popover or menu popup anchored inside the element is the overlay's. A touch on a slider, scrollbar, text input or color picker keeps its press, so it makes no request. Input reads are unchanged: the key and the buttons still read as pressed. Setting a handler on another app's element raises, and so does setting one on a card button's content, whose presses and focus are the card's.

UiElement:focus

method
UiElement:focus()

Move keyboard focus to this element. Takes effect on focusable widgets (a text input, button, checkbox, slider, radio group, or list); a no-op on elements that cannot hold focus. The focus ring shows when the last input came from the keyboard or a gamepad. Called while its app is not the input owner (another app is focused, or a workstation dialog is open), it moves no keys: the element gets focus when its app is the input owner again.

UiElement:set_text_color

method
UiElement:set_text_color(color?: Color | ThemedColor)

Set the color of a label's or link's text, or of a button's label. Pass a datumhue.theme.token(...) to follow the theme; omit / pass nil to re-follow the default text color. A disabled button shows the theme's disabled label color, and this one again once enabled.

UiElement:set_material

method
UiElement:set_material(material?: Shader)

Apply a shader material. Omit / pass nil to remove it.

UiElement:set_source

method
UiElement:set_source(source?: Image | DrawCanvas | Scene)

Swap the image texture to a render-target source. Omit / pass nil to clear it.

Example
local first = datumhue.draw.new({ width = 64, height = 64 })
local view = first:mount({ parent = panel, width = 128, height = 128 })
local second = datumhue.draw.new({ width = 64, height = 64 })
second:circle({ radius = 24, color = datumhue.color("#a7c080") })
view:set_source(second)

UiElement:update

method
UiElement:update(opts: ui.ElemUpdateOptions)

Batch-update layout / style plus source, color, text, visible, or material. The sizes, padding, border width, corner radius, gaps, colors and fonts it writes stay as written through later theme and density changes. On a button, text and the font keys style its label. On a text input, the room its leading_icon, line numbers and clear button take adds to the padding it writes, so a percentage on a side one of them takes raises. A column flex_direction, a wrapping flex_wrap or a block or grid display raises too.

Example
local status = panel:label({ text = "Draft" })
status:update({
    text = "Saved",
    font_weight = "bold",
    padding = 4,
    color = datumhue.theme.token("success_background"),
})

opts ui.ElemUpdateOptions options 43 keys, 34 inherited Omitted keys keep their current value.

KeyTypeDescription
sourceImage | DrawCanvas | SceneTexture source handle (Image / DrawCanvas / Scene); swaps the image.
colorColor | ThemedColorBackground color. Pass a datumhue.theme.token(...) to follow the theme.
textstring | MessageNew text — a plain string (stops following any locale), or a datumhue.i18n.t(...) message (follows the active locale). Relabels a button or link in place; raises on a card button. On a code element a string replaces its source, as UiElement.text does, and a message raises.
font_sizenumber | stringNew label font size: a number (pixels) or a unit string. Pins the size (stops following the theme).
font_weightnumber | "thin" | "extralight" | "light" | "regular" | "normal" | "medium" | "semibold" | "bold"New label weight: a number on the variable weight axis, or a weight name (bold is the heaviest). Pins the weight.
font_style"normal" | "italic" | "oblique"New label face: upright, the calligraphic cursive, or a mechanical slant.
fontFontNew font (a bytes:font() / datumhue.font.builtin() handle); pins the face.
visiblebooleanShow or hide the element.
materialShaderShader material.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:mouse_position

method
UiElement:mouse_position() -> Vec2?

Cursor position in UI px from the top-left of the element's border box (bounds.min), at every zoom and stretch mode; negative or past bounds.size off the element. Nil off the window and while the element has no bounds.

UiElement:to_texture_coords

method
UiElement:to_texture_coords(pos: Vec2) -> Vec2?

Scale an element-relative position in UI px (what mouse_position returns) to the source's own pixels, or nil: an image's pixels, or a draw canvas's or scene's width and height, whatever resolution it renders at. Scene:screen_to_ray takes these pixels.

Example
local photo = datumhue.image.new({ width = 64, height = 64 })
local view = photo:mount({ parent = panel, width = 128, height = 128 })
-- Nil until the element's first layout.
datumhue.time.wait_frames(1)
local center = datumhue.math.vec2(64, 64)
print(view:to_texture_coords(center)) --> vec2(32.0, 32.0)

UiElement:panel

method
UiElement:panel(opts: ui.PanelOptions) -> UiElement

Create a child UI panel. Inside a card button it raises for on_press, on_release or on_double_click: the card's presses are its own.

Example
local row = panel:panel({
    flex_direction = "row",
    gap = 8,
    padding = { left = 12, right = 12 },
    width = "100%",
    color = datumhue.theme.token("surface_raised"),
})
row:label({ text = "Left" })
row:label({ text = "Right" })

opts ui.PanelOptions options 41 keys, 34 inherited

KeyTypeDefaultDescription
colorColor | ThemedColorthe theme's panel background, following theme switchesBackground color. Pass a datumhue.theme.token(...) to follow the theme and repaint on change.
materialShaderthe panel's plain backgroundShader material applied to the panel.
on_hoverfunctionno defaultCalled when a pointer (the mouse or a touch) enters the panel or any of its descendants.
on_hover_exitfunctionno defaultCalled when the last pointer leaves the panel and all of its descendants; a press held inside it keeps it entered until the press ends.
on_pressfunctionno defaultCalled when a pointer (the mouse or a touch) presses the panel with its primary button.
on_releasefunctionno defaultCalled when that press ends: released anywhere, or taken by a drag, a drag scroll or a long press.
on_double_clickfunctionno defaultCalled on a double-click: two presses on the panel within the double-click window.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:button

method
UiElement:button(opts: ui.ButtonOptions) -> UiElement

Create a child interactive button. Without text the button is a card: the elements created under it form its content, the whole card is one focus stop and one pointer target (a press on any of its content activates it), and creating an interactive widget inside it raises, a table and a book page with links, widgets or chapter navigation included, as does a press or drag handler on its content. A button with text has a border of the theme's border_width unless border is set, outside its padding, so a button sized by its content is that much larger on each side; a card has a border only when border is set. A button that takes the theme's fills draws its border in the theme's button_border, which stands out from the surface under it. A disabled button, a button given a fill and a button in a menu draw their border in their fill.

Example
panel:button({
    text = "Save",
    on_click = function()
        print("saved")
    end,
})

local card = panel:button({ accessible_label = "Open notes" })
card:label({ text = "Notes" })

opts ui.ButtonOptions options 45 keys, 34 inherited

KeyTypeDefaultDescription
textstring | Messagethe button is a card, whose content is the elements created under itButton label — a plain string, or a datumhue.i18n.t(...) message that follows the active locale.
accessible_labelstring | Messagethe label's text names the buttonThe name assistive technology announces, in place of the label's text. Required without text. A datumhue.i18n.t(...) message follows the active locale.
focusablebooleantrueWhether the button takes focus: a navigation stop with a focus ring. false leaves it activated by pointer and touch only; it keeps its accessible name.
enabledbooleantrueWhether the button starts enabled. A disabled button is grayed out and ignores clicks and keyboard activation until elem.enabled = true.
keep_input_focusbooleanfalseWhether pressing the button leaves a focused text input of your app focused, keeping its caret, its selection and a phone's on-screen keyboard. Otherwise the press takes focus from the input, as a press anywhere outside it does.
on_clickfunctionno defaultClick callback.
on_hoverfunctionno defaultCalled when a pointer (the mouse or a touch) enters the button or any of its descendants.
on_hover_exitfunctionno defaultCalled when the last pointer leaves the button and all of its descendants; a press held inside it keeps it entered until the press ends.
on_pressfunctionno defaultCalled when a pointer (the mouse or a touch) presses the button with its primary button.
on_releasefunctionno defaultCalled when that press ends: released anywhere, or taken by a drag, a drag scroll or a long press.
on_double_clickfunctionno defaultCalled on a double-click: two presses on the button within the double-click window.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:open_button

method
UiElement:open_button(opts: ui.OpenButtonOptions) -> UiElement

Create a child button that opens a file dialog when clicked, and calls on_pick with what the person picks. Only the click opens the dialog, so your app cannot open one by itself. In a browser that does not let a web page change the person's files, a picked folder is read-only.

Example
panel:open_button({
    text = "Open image",
    mode = "file",
    filters = { { "Images", { "png", "jpg" } } },
    on_pick = function(file)
        print(file.name)
    end,
})

opts ui.OpenButtonOptions options 41 keys, 34 inherited

KeyTypeDefaultDescription
text requiredstring | Messageno defaultButton label (required) — a plain string, or a datumhue.i18n.t(...) message that follows the active locale.
mode required"file" | "files" | "dir"no defaultWhat the click picks; selects the on_pick payload.
filterstable[]every file showsFile-type filters, an array of {label, {ext, ...}} pairs. Valid only for file modes; raises with a directory mode.
start_dirDirthe dialog's own starting locationSeeds the dialog's starting location. Best-effort: native dialogs open there; browser pickers ignore it.
titlestringthe dialog's own titleDialog title.
on_pickfunctionno defaultCalled on a successful pick with the picked handle (never on cancel). Optional; bind later with elem:on_pick (e.g. from a hosting app via book:element). With no handler bound a click opens no dialog.
enabledbooleantrueWhether the button starts enabled. Setting false grays it out and ignores clicks.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:save_button

method
UiElement:save_button(opts: ui.SaveButtonOptions) -> UiElement

Create a child button that opens a save dialog when clicked and atomically writes source to the chosen file. The click is the user gesture. On success on_save fires with the read-only File handle; cancel fires nothing. With no source set the click does nothing.

Example
panel:save_button({
    text = "Export",
    source = "name,score\nAda,3\n",
    suggested_name = "scores.csv",
    filters = { { "CSV", { "csv" } } },
    on_save = function(file)
        print("saved " .. file.name)
    end,
})

opts ui.SaveButtonOptions options 42 keys, 34 inherited

KeyTypeDefaultDescription
text requiredstring | Messageno defaultButton label (required) — a plain string, or a datumhue.i18n.t(...) message that follows the active locale.
sourcestring | Bytesa click does nothing until elem:on_save_source sets a payloadThe bytes to write, captured now. The write is atomic, on the one click. Bind them later with elem:on_save_source instead to keep the payload current.
suggested_namestringthe dialog pre-fills no nameFilename pre-filled in the save dialog.
filterstable[]every file showsFile-type filters, an array of {label, {ext, ...}} pairs.
start_dirDirthe dialog's own starting locationSeeds the dialog's starting location. Best-effort: native dialogs open there; browser pickers ignore it.
titlestringthe dialog's own titleDialog title.
on_savefunctionno defaultCalled after the bytes are committed to the chosen file, with the read-only File handle (never on cancel). Optional; bind later with elem:on_save.
enabledbooleantrueWhether the button starts enabled.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:copy_button

method
UiElement:copy_button(opts: ui.CopyButtonOptions) -> UiElement

Create a child button that places its source text on the system clipboard when clicked. The click is the user gesture — there is no programmatic copy, so an app can neither read nor spam the clipboard on its own. On success on_copy fires; a failed write logs. With no source set the click does nothing.

Example
panel:copy_button({
    label = "Copy color",
    source = "#e67e80",
    on_copy = function()
        print("copied")
    end,
})

opts ui.CopyButtonOptions options 37 keys, 34 inherited

KeyTypeDefaultDescription
label requiredstring | Messageno defaultButton label (required) — a plain string, or a datumhue.i18n.t(...) message that follows the active locale.
sourcestring | Bytesa click does nothing until elem:on_copy_source sets a payloadThe text a click places on the clipboard, captured now. A string or UTF-8 Bytes (non-UTF-8 raises — the clipboard is text). Bind it later with elem:on_copy_source instead to keep the payload current.
on_copyfunctionno defaultCalled after a click places the text on the clipboard. In the browser the write is asynchronous — it fires when the write is handed to the clipboard, and a rejected write logs instead.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:link

method
UiElement:link(opts: ui.LinkOptions) -> UiElement

Create a child link styled as link text. Clicking it opens url in a new browser tab; opening only ever happens from this user click, so an app cannot open URLs on its own. url must be http, https, or mailto (any other scheme raises). Hovering shows the destination host.

Example
panel:link({
    text = "Lua manual",
    url = "https://www.lua.org/manual/5.5/",
})

opts ui.LinkOptions options 36 keys, 34 inherited

KeyTypeDefaultDescription
text requiredstring | Messageno defaultLink text (required) — a plain string, or a datumhue.i18n.t(...) message that follows the active locale.
url requiredstringno defaultDestination URL (required). Must be http, https, or mailto; any other scheme raises. Clicking opens it in a new browser tab — there is no programmatic open, so a user gesture is always required.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:date_picker

method
UiElement:date_picker(opts: ui.DatePickerOptions) -> UiElement

Create a date picker: a field that opens a calendar popover. Selecting a day fires on_change with the selected instant (Unix microseconds); read/write it via value (nil until picked). Build / decode the value with time.from_calendar / time.calendar.

Example
local start = datumhue.time.from_calendar({ year = 2026, month = 10 })
panel:date_picker({
    initial = start,
    min = start,
    on_change = function(micros)
        local day = datumhue.time.calendar(micros)
        print(day.year, day.month, day.day)
    end,
})

opts ui.DatePickerOptions options 40 keys, 34 inherited

KeyTypeDefaultDescription
initialintegernothing is selected; the calendar opens on today's month and the time starts at 00:00Initially-selected instant (Unix microseconds, UTC).
minintegerno day is too earlyEarliest selectable instant (Unix microseconds); earlier days are disabled.
maxintegerno day is too lateLatest selectable instant (Unix microseconds); later days are disabled.
enabledbooleantrueWhether it starts enabled.
openbooleanfalseStart with the popover open.
on_changefunctionno defaultCalled with the selected instant (Unix microseconds) when the user picks a day or edits the time. A programmatic value write does not fire it.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:time_picker

method
UiElement:time_picker(opts: ui.DatePickerOptions) -> UiElement

Create a time picker: a field that opens a popover with 24-hour HH:MM steppers. Editing the time fires on_change with the selected instant (Unix microseconds).

Example
panel:time_picker({
    initial = datumhue.time.from_calendar({ year = 2026, hour = 9 }),
    on_change = function(micros)
        print(datumhue.time.calendar(micros).hour)
    end,
})

opts ui.DatePickerOptions options 40 keys, 34 inherited

KeyTypeDefaultDescription
initialintegernothing is selected; the calendar opens on today's month and the time starts at 00:00Initially-selected instant (Unix microseconds, UTC).
minintegerno day is too earlyEarliest selectable instant (Unix microseconds); earlier days are disabled.
maxintegerno day is too lateLatest selectable instant (Unix microseconds); later days are disabled.
enabledbooleantrueWhether it starts enabled.
openbooleanfalseStart with the popover open.
on_changefunctionno defaultCalled with the selected instant (Unix microseconds) when the user picks a day or edits the time. A programmatic value write does not fire it.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:datetime_picker

method
UiElement:datetime_picker(opts: ui.DatePickerOptions) -> UiElement

Create a combined date+time picker: a calendar popover with 24-hour HH:MM steppers. Picking a day or editing the time fires on_change with the selected instant (Unix microseconds).

Example
panel:datetime_picker({
    initial = datumhue.time.unix_micros(),
    on_change = function(micros)
        print(micros)
    end,
})

opts ui.DatePickerOptions options 40 keys, 34 inherited

KeyTypeDefaultDescription
initialintegernothing is selected; the calendar opens on today's month and the time starts at 00:00Initially-selected instant (Unix microseconds, UTC).
minintegerno day is too earlyEarliest selectable instant (Unix microseconds); earlier days are disabled.
maxintegerno day is too lateLatest selectable instant (Unix microseconds); later days are disabled.
enabledbooleantrueWhether it starts enabled.
openbooleanfalseStart with the popover open.
on_changefunctionno defaultCalled with the selected instant (Unix microseconds) when the user picks a day or edits the time. A programmatic value write does not fire it.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:color_picker

method
UiElement:color_picker(opts: ui.ColorPickerOptions) -> UiElement

Create a color picker: a field (a swatch + the hex string) that opens a popover with a saturation/value square, a hue strip, a live preview, a hex field, and optional preset swatches. Dragging, committing the hex, or clicking a preset fires on_change with the picked color (sRGB). Read or write the current color via the value property; a programmatic write is silent.

Example
panel:color_picker({
    initial = datumhue.color("#7fbbb3"),
    alpha = true,
    swatches = { datumhue.theme.token("accent_primary") },
    on_change = function(color)
        print(color)
    end,
})

opts ui.ColorPickerOptions options 40 keys, 34 inherited

KeyTypeDefaultDescription
initialColor | ThemedColordatumhue.color(1, 1, 1)Initial color (a Color or a theme token).
alphabooleanfalseAdd an alpha strip and widen the hex field to #RRGGBBAA; false keeps the color fully opaque.
swatches(Color | ThemedColor)[]no preset rowPreset swatches shown as a clickable row (colors or theme tokens). Clicking one sets the color and keeps the popover open.
enabledbooleantrueWhether it starts enabled.
openbooleanfalseStart with the popover open.
on_changefunctionno defaultCalled with the picked Color when the user drags the square/strips, commits the hex field, or clicks a preset. A programmatic value write does not fire it.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:checkbox

method
UiElement:checkbox(opts: ui.CheckboxOptions) -> UiElement

Create a child checkbox. State is the checked property; on_change fires on user toggles.

Example
panel:checkbox({
    label = "Show grid",
    checked = true,
    on_change = function(checked)
        print(checked)
    end,
})

opts ui.CheckboxOptions options 38 keys, 34 inherited

KeyTypeDefaultDescription
labelstringa bare checkboxLabel shown beside the box.
checkedbooleanfalseWhether it starts checked.
enabledbooleantrueWhether it starts enabled. A disabled checkbox ignores clicks and keyboard toggling until elem.enabled = true.
on_changefunctionno defaultCalled with the new checked state (boolean) each time the user toggles it. A programmatic elem.checked = ... write does not fire it.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:slider

method
UiElement:slider(opts: ui.SliderOptions) -> UiElement

Create a child horizontal slider. State is the value property; on_change fires continuously while dragging.

Example
panel:slider({
    min = 0,
    max = 100,
    step = 5,
    value = 50,
    width = 200,
    on_change = function(value)
        print(value)
    end,
})

opts ui.SliderOptions options 40 keys, 34 inherited

KeyTypeDefaultDescription
valuenumberthe range midpointInitial value, clamped to [min, max].
minnumber0Range minimum.
maxnumber1Range maximum.
stepnumber0.1Keyboard / track-click step increment.
enabledbooleantrueWhether it starts enabled. A disabled slider ignores drag and keyboard input until elem.enabled = true.
on_changefunctionno defaultCalled with the new value (number) continuously as the user drags or steps it. A programmatic elem.value = ... write does not fire it.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:number_input

method
UiElement:number_input(opts: ui.NumberInputOptions) -> UiElement

Create a child numeric input: a number field with -/+ steppers. State is the value property; on_change fires on field edits and steps.

Example
panel:number_input({
    value = 2.5,
    min = 0,
    step = 0.5,
    precision = 1,
    on_change = function(value)
        print(value)
    end,
})

opts ui.NumberInputOptions options 41 keys, 34 inherited

KeyTypeDefaultDescription
valuenumbermin, or 0 without a minInitial value, clamped to the range.
minnumberthe value is unbounded belowMinimum value.
maxnumberthe value is unbounded aboveMaximum value.
stepnumber1Amount the -/+ steppers add or subtract.
precisioninteger0Decimal places the value is displayed and snapped to, 0..10.
enabledbooleantrueWhether it starts enabled. A disabled number input ignores typing and the steppers until elem.enabled = true.
on_changefunctionno defaultCalled with the new value (number) when the user edits the field or uses a stepper. A programmatic elem.value = ... write does not fire it.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:radio_group

method
UiElement:radio_group(opts: ui.RadioGroupOptions) -> UiElement

Create a child radio group. State is the selected property (1-based); on_change fires on user selection.

Example
panel:radio_group({
    options = { "Small", "Medium", "Large" },
    selected = 2,
    on_change = function(index)
        print(index)
    end,
})

opts ui.RadioGroupOptions options 39 keys, 34 inherited

KeyTypeDefaultDescription
options requiredstring[]no defaultOption labels (required); one radio button per entry, in order.
selectedintegerno option is selected1-based index of the initially-selected option.
accessible_labelstring | Messagethe group has no name of its ownThe name assistive technology announces for the group; a label beside it does not name it. A datumhue.i18n.t(...) message follows the active locale. Also a read/write property.
enabledbooleantrueWhether it starts enabled. A disabled group ignores clicks and keyboard navigation until elem.enabled = true.
on_changefunctionno defaultCalled with the selected 1-based index (integer) when the user picks an option. A programmatic elem.selected = ... write does not fire it.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:list

method
UiElement:list(opts: ui.ListOptions) -> UiElement

Create a child selectable list. State is the selected property (a 1-based index, or an index array when multi); on_change fires on user selection.

Example
panel:list({
    items = { "Apple", "Pear", "Plum" },
    multi = true,
    selected = { 1, 3 },
    on_change = function(indices)
        print(#indices)
    end,
})

opts ui.ListOptions options 39 keys, 34 inherited

KeyTypeDefaultDescription
items requiredstring[]no defaultRow labels (required); one selectable row per entry, in order.
selectedinteger[]no row is selectedInitially-selected 1-based indices. A single-element array selects one row; several pre-select multiple (multi-select only).
multibooleanfalseAllow selecting multiple rows at once.
enabledbooleantrueWhether it starts enabled. A disabled list ignores clicks and keyboard navigation until elem.enabled = true.
on_changefunctionno defaultCalled on user selection with the new selected value — a 1-based integer for a single-select list, a 1-based index array for multi. A programmatic elem.selected = ... write does not fire it.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:table

method
UiElement:table(opts: ui.TableOptions) -> UiElement

Create a child data table bound to a DataHandle: a header row over a scrolling body that reads only the rows in view, copying no row into Lua. State is the selected property (a 1-based display index, or an index array when multi); on_change fires on user selection. tbl:row(i) reads one row on demand.

Example
local scores = datumhue.bytes("name,score\nAda,3.25\nLin,5.5\n"):csv()
panel:table({
    data = scores,
    columns = {
        { column = "name", label = "Name" },
        { column = "score", label = "Score", format = "{:.1}" },
    },
    sortable = true,
    height = 120,
})

opts ui.TableOptions options 39 keys, 34 inherited

KeyTypeDefaultDescription
data requiredDataHandleno defaultThe tabular data to display (required). Only the rows in the visible scroll window render, and no row is copied into Lua; tbl:row(i) reads a single row on demand.
columns requiredui.TableColumn[]no defaultColumn specs (required, in display order). Raises when a named column is missing from already-loaded data.
sortablebooleanfalseMake the headers clickable: a click sorts by that column (stable), a second click on the same header reverses. The selection follows the rows across a sort.
multibooleanfalseAllow selecting multiple rows at once.
on_changefunctionno defaultCalled on user row selection with the new selected value — a 1-based display index for a single-select table, an index array when multi. A programmatic elem.selected = ... write does not fire it.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:text_input

method
UiElement:text_input(opts: ui.TextInputOptions) -> UiElement

Create a child text input. Its content is the text property; on_change fires on each user edit and on_submit on Enter (single-line). Its children share the row inside its padding with its text, after it unless reorder lists them first, and its layout keys act on that row. A column flex_direction, a wrapping flex_wrap or a block or grid display raises, here and in update, as does assigning a block or grid display. In a row aligning its items at their baselines, a single-line text input aligns at the bottom of its line of text, where its caret ends. A multi-line one aligns at the bottom of the area inside its padding. A child laid out in the row with its text can change this; a popover, a child whose display is none and one given left, top, right or bottom in its options or update cannot. The first such child aligned at its baseline (by its align_self or the input's align_items) gives the input its baseline, or, with none, the first such child reorder lists before the text. An auto top or bottom margin keeps a child from aligning at its baseline.

Example
panel:text_input({
    placeholder = "Search",
    clearable = true,
    width = 240,
    on_submit = function(text)
        print(text)
    end,
})

opts ui.TextInputOptions options 55 keys, 34 inherited

KeyTypeDefaultDescription
valuestring""Initial text content. Read/write later via the text property.
placeholderstringthe empty field shows no placeholderPlaceholder shown while the field is empty.
accessible_labelstring | Messagethe input has no name of its ownThe name assistive technology announces for the input; its placeholder is not announced in its place. A datumhue.i18n.t(...) message follows the active locale. Also a read/write property.
leading_iconImageno iconAn image drawn inside the field's border at its start, one text line tall, as a mask in the theme's secondary text color; a press on it focuses the field. Also a read/write property.
clearablebooleanfalseShow a clear button at the field's end while it holds text. Raises on a multi-line input. Also a read/write property.
max_lengthintegerno length limitMaximum number of characters the field accepts; a negative number sets no limit.
multilinebooleanfalseAllow multiple lines: Enter inserts a newline instead of submitting.
tab_spacesintegerTab moves focus, as on every other inputOn a multi-line input, Tab types this many spaces at the caret, or over a selection of part of a line. With a whole line or several lines selected, it adds them to the start of each of those lines that is not empty. Shift+Tab takes up to as many off the start of each line the selection or the caret is on. Escape leaves the input. Raises on a single-line input, or for a number below 1 or above 16.
syntax"lua"plain text in the input's text colorColors the text as source code of this language as it is typed, in the theme's syntax_* colors. In a multi-line input, Enter starts the new line indented as the line above it; a single-line input still calls on_submit on Enter. A disabled input shows its text in one color.
line_numbersbooleanfalseNumbers the lines in a column at the input's start that scrolls with the text; a wrapped line is numbered on its first row. After the numbers the column keeps room for one more character, where line_marks show their signs. Raises on a single-line input, with leading_icon, and when padding gives the start a percentage.
ligaturesbooleantrueWhether the font may join signs such as >= or -> into one shape. False draws each character as typed, in the text, the placeholder, the line numbers and any element inside the input.
filterstringevery character can be typedA Lua character-class pattern (e.g. "%d", "[%d.-]") restricting which characters can be typed or pasted, matched per character. It masks the character *set*, not structure: validate the whole value in on_change or on_submit (and prefer number_input for numbers). ASCII-oriented like Lua's own %-classes; raises if the pattern is malformed.
purpose"text" | "search" | "email" | "url" | "number""text"What the field holds, which picks a browser's on-screen keyboard and the label of its Enter key. A filter that takes only digits, or digits and both decimal separators, wins: the field keeps its digit keypad and the purpose changes nothing. It never limits what can be typed; outside a browser it changes nothing.
select_all_on_focusbooleanfalseSelect all the text when the field gains keyboard focus.
enabledbooleantrueWhether it starts enabled. A disabled input ignores keyboard and pointer input until elem.enabled = true.
on_changefunctionno defaultCalled with the new text (string) on each user edit. A programmatic elem.text = ... write does not fire it.
on_submitfunctionno defaultCalled with the text (string) when Enter is pressed in a single-line input.
autofocusbooleanfalseTake keyboard focus as soon as the input is created, as focus() does: created while its app is not the input owner, it moves no keys and is the focus the app gets back once it is. If several inputs request it in one frame, the last one created wins.
on_focusfun(e: ui.FocusEvent)no defaultCalled when keyboard focus enters the input, as on_focus.
on_blurfun(e: ui.FocusEvent)no defaultCalled when keyboard focus leaves the input (clicking away, Tab, or the app losing focus), as on_blur.
on_cancelfun(e: ui.CancelEvent)no defaultCalled when a cancel request reaches the focused input: Escape (also while datumhue.ui.focus_navigation is off), gamepad B or Back. The input then loses focus, so the next request goes on past it.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:popover

method
UiElement:popover(opts: ui.PopoverOptions) -> UiElement

Create an overlay popover anchored to this element, positioned on side/align with gap, auto-flipped to stay on-screen. Fill it with child widgets; show/hide via the visible property.

Example
local help = panel:button({ text = "Help" })
local tip = help:popover({
    side = "right",
    gap = 8,
    padding = 8,
    visible = true,
    light_dismiss = true,
})
tip:label({ text = "Drag to pan, scroll to zoom." })

opts ui.PopoverOptions options 39 keys, 33 inherited

KeyTypeDefaultDescription
side"top" | "bottom" | "left" | "right""bottom"Which side of the anchor to place it. Auto-flips to the opposite side if it would clip the window.
align"start" | "center" | "end""start"Alignment along the axis perpendicular to side.
gapnumber4Gap in UI px between the anchor and the popover.
visiblebooleanfalseWhether it starts shown; toggle later via the visible property.
light_dismissbooleanfalseClose the popover on a pointer press outside it. Off: fully app-controlled via the visible property. On: light-dismisses like a menu (pressing the anchor still toggles it).
navigation"auto" | "modal" | "none""auto"How it scopes focus navigation in your app. Light dismiss works the same in each.

Also takes the other 33 keys of ui.NodeStyle.

UiElement:menu

method
UiElement:menu(opts: ui.MenuOptions) -> UiElement

Create a dropdown menu anchored to this element: a label trigger button and a popup you fill with child widgets (each item carries its own callback). Clicking the button toggles the popup; clicking outside or pressing Escape closes it; activating a button item runs it and closes the menu.

Example
local file = panel:menu({ label = "File" })
file:button({
    text = "New",
    on_click = function()
        print("new")
    end,
})
file:button({ text = "Quit", on_click = datumhue.app.terminate })

opts ui.MenuOptions options 36 keys, 34 inherited

KeyTypeDefaultDescription
label requiredstringno defaultThe menu button's label.
enabledbooleantrueWhether it starts enabled.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:label

method
UiElement:label(opts: ui.LabelOptions) -> UiElement

Create a child text label.

Example
panel:label({
    text = "Chapter one",
    font_size = "1.5rem",
    font_weight = "bold",
})
panel:label({
    text = "A long description of the chapter.",
    max_lines = 2,
})

opts ui.LabelOptions options 45 keys, 34 inherited

KeyTypeDefaultDescription
textstring | Messagean empty labelText content — a plain string, or a datumhue.i18n.t(...) message that follows the active locale and re-renders when it changes.
font_sizenumber | stringthe theme's normal text size, following theme switchesFont size: a number (pixels) or a unit string like "1.5rem"/ "50vw"/"4vmin".
colorColor | ThemedColorthe theme's text color, following theme switchesText color. Pass a datumhue.theme.token(...) to follow the theme and repaint on change.
strikethroughbooleanfalseDraw a line through the text. Also a read/write property.
underlinebooleanfalseDraw a line under the text. Also a read/write property.
font_weightnumber | "thin" | "extralight" | "light" | "regular" | "normal" | "medium" | "semibold" | "bold"the theme's normal text weight, following theme switchesFont weight: a number on the variable weight axis, or a weight name (bold is the heaviest).
font_style"normal" | "italic" | "oblique""normal"Font face: upright, the calligraphic cursive, or a mechanical slant.
fontFontthe app's default faceFont (a bytes:font() / datumhue.font.builtin() handle); pins this label's face over the one the app sets as its default font.
ligaturesbooleantrueWhether the font may join signs such as >= or -> into one shape. False draws each character as typed, in the text and in any element inside the label.
max_linesintegerno line limitThe most lines the label shows, at least 1. A longer text ends in "..." on the last line, cut at a glyph; a word wider than the label breaks at a glyph. Also a read/write property.
overflow_wrap"break_word" | "normal""break_word"How the label breaks a word wider than itself; max_lines and marquee break their own way while set. Also a read/write property.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:code

method
UiElement:code(opts: ui.CodeOptions) -> UiElement

Create a child code element: source code drawn as a book page draws a code listing, on the theme's surface_sunken fill in its normal text size, following theme and density switches. It wraps at its width, between two characters where a run with no space is wider than it. It takes no focus, keys or presses, so a press on it reaches the element holding it, and it may go inside a card button. Assistive technology reads it as code, with its source as the value.

Example
panel:code({
    text = 'local name = "Hue"\nprint("Hello, " .. name)',
    syntax = "lua",
    line_numbers = true,
    width = 320,
})

opts ui.CodeOptions options 38 keys, 34 inherited

KeyTypeDefaultDescription
textstring""The source shown. Read and write it later as the text property.
syntax"lua"plain text in the theme's text colorColors the source as code of this language, in the theme's syntax_* colors.
line_numbersbooleanfalseNumbers the lines in a column at the element's start, in the theme's secondary text color; a wrapped line is numbered on its first row. After the numbers the column keeps room for one more character, where line_marks show their signs.
ligaturesbooleantrueWhether the font may join signs such as >= or -> into one shape. False draws each character as typed, in the source, its line numbers and any element inside it.

Also takes every key of ui.NodeStyle (34 keys).

UiElement:touch_stick

method
UiElement:touch_stick(opts?: ui.TouchStickOptions) -> UiElement

Create a child touch stick: a thumb stick a touch drives. The element's box is the area a touch can start it in; an axis the style leaves unsized is 2 * radius UI px. A touch that starts on it is the stick's until it ends: input.touches() and the elements under it never see it. While it holds the stick, stick, deflection and pressed follow it, and the stick adds to your app's input.action_vector past a 0.25 dead zone. A mouse click never moves it. Raises inside a card button.

Example
-- A row for the controls, the stick on its left.
local controls = panel:panel({
    flex_direction = "row",
    width = 320,
    height = 160,
    padding = 16,
})
local move = controls:touch_stick({ width = 160, radius = 48 })
-- At rest until a touch starts on it; y is up.
print(move.stick, move.deflection) --> vec2(0.0, 0.0) 0.0

opts ui.TouchStickOptions options 40 keys, 34 inherited

KeyTypeDefaultDescription
radiusnumber56How far from the base's center, in UI px, stick reaches length 1. Must be finite and greater than 0.
floatingbooleantrueWhether the base appears where the touch lands inside the element; otherwise it sits at the element's center.
outer_ringnumberno ring is drawnA ring drawn at this multiple of radius, lit in the theme's accent color while deflection is past it. Must be finite and greater than 1.
baseImagea themed circleArt for the base, drawn 2 * radius across.
knobImagea themed discArt for the knob, drawn radius across.
touch_onlybooleantrueWhether the stick shows only while input.last_source is "touch".

Also takes every key of ui.NodeStyle (34 keys).

UiElement:touch_button

method
UiElement:touch_button(opts: ui.TouchButtonOptions) -> UiElement

Create a child touch button that holds a named action down while a touch holds it: from the touch's start until it ends, wherever the finger moves, input.is_action_down reads true, and is_action_pressed and is_action_released each read true in one tick, whether or not bind_action bound the name. A touch that starts on it is the button's until it ends; the mouse and keys never press it. Removing, hiding or disabling a held button releases it. Raises inside a card button.

Example
panel:touch_button({
    action = "jump",
    text = "Jump",
    width = 72,
    height = 72,
})
datumhue.on_update(function(dt)
    -- The button presses "jump" whether or not it is bound.
    if datumhue.input.is_action_pressed("jump") then
        print("jump")
    end
end)

opts ui.TouchButtonOptions options 38 keys, 34 inherited

KeyTypeDefaultDescription
action requiredstringno defaultThe named action this button presses. Must not be empty.
textstring | Messagethe button is a card, whose content is the elements created under itThe label: a plain string, or a datumhue.i18n.t(...) message that follows the active locale.
accessible_labelstring | Messagethe label's text names the buttonThe name assistive technology announces. Required without text. A datumhue.i18n.t(...) message follows the active locale.
touch_onlybooleantrueWhether the button shows only while input.last_source is "touch".

Also takes every key of ui.NodeStyle (34 keys).

UiElement:reveal_line

method
UiElement:reveal_line(line: integer)

Scroll a multi-line text input so its 1-based line shows. While the line's first row is not wholly in view, the text scrolls to put that row in the middle, or as near as the text's ends allow; a line already in view leaves the text where it is. Then the scroll containers holding the input move, innermost first, as for an element Tab focuses, so the line shows in the window and above an on-screen keyboard. The caret, the selection and focus stay where they are, no keyboard opens and on_change does not fire. Called before the input is first laid out, it takes effect once it is. Raises on a single-line text input or any other element, and for a line below 1 or past the last.

Example
local lines = {}
for n = 1, 40 do
    lines[n] = "print(" .. n .. ")"
end
local editor = panel:text_input({
    multiline = true,
    line_numbers = true,
    value = table.concat(lines, "\n"),
    width = 320,
    height = 120,
})
editor:reveal_line(30)

datumhue.com

(c) 2026 Shuusei AB. Quote excerpts of this manual, with attribution to DatumHue, in your own documentation, tutorials, course materials, reviews, and other writing or video. Republishing it in whole or in substantial part needs written permission: support@datumhue.com.

Victor Mono is licensed under the SIL Open Font License 1.1
Copyright (c) 2024, Rune Bjørnerås (https://github.com/rubjo)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Try

    ↑↓ selectEnter openTab filter

    Settings

    Theme
    Text
    Print

    Keyboard shortcuts

    / or Ctrl KSearch
    ↑ ↓ EnterPick a result
    TabNext result filter
    j kNext or previous entry
    [ ]Previous or next page
    yCopy a link to the current entry
    ?These shortcuts
    EscClose

    On this page