DOCUMENTATION / sdk/text-input.md
Text input
Text entry uses the operating system's committed Unicode input. Declare a single field before displaying its screen, keep the last returned value, and close the field when leaving that screen. This optional host service works with existing profiles; cartridges that do not use it retain their input behavior.
Luau exposes byt.text_input.configure(options), byt.text_input.read() and
byt.text_input.close(). Odin imports text "sdk:text" and provides the same
operations. configure returns a boolean and takes id, value, max_bytes,
x, y, width, height, selection_start and selection_end. The ID must be
nonzero. Coordinates use the game's logical canvas. Selection offsets count
UTF-8 bytes; Luau defaults both offsets to the end, while Odin sets them explicitly.
Call configure when opening or programmatically changing the field. Calling it
every tick discards host edits and restarts the session. Values are valid UTF-8,
single line, at most 256 bytes, with a field-specific cap from 1 to 256 bytes.
ASCII control characters are rejected. Selection endpoints must be ordered and
lie on UTF-8 boundaries. An oversized edit is rejected in full.
read returns the latest committed value and selection, plus changed,
submitted and cancelled. The snapshot is available once per update tick;
event flags drain on that successful read and are false on subsequent ticks.
Multiple host edits between ticks coalesce into the latest bounded snapshot.
An insufficient destination leaves the snapshot pending. Odin takes a caller
buffer of at least text.MAX_BYTES and returns (Event, bool); Event.value
borrows that buffer until its next use. Luau returns an event table or nil.
IME preedit stays with the operating system and never becomes committed game
text. Backspace edits the selection or preceding Unicode code point on native;
the browser uses its platform editor. Enter submits and Escape cancels, except
while an IME composition is active. Both deactivate the host editor while
retaining the final value for reads. Cancellation reports the current committed
value; restoring the original value is the game's decision. close discards all
pending state. Games own their character filtering and final acceptance rules.
Native editing also supports Left/Right, Home/End, Shift selection, Delete,
Ctrl+A and Ctrl+C/X/V. Selection moves by Unicode code point. Pasting a value
that exceeds the field's byte limit leaves both value and selection unchanged.
On native, SDL text input starts when the field is configured, and keyboard game controls are suppressed while editing. Pointer controls remain available. Losing window focus cancels native editing and retains the committed value. On browser, a real DOM input overlays the declared field so its focus occurs synchronously inside the trusted pointer gesture. Declare the field before the tap: a worker response after a tap cannot reliably summon a phone keyboard. Composition, selection and clipboard behavior remain browser-owned. A focused field blocks game keyboard controls. Replacing or unloading the game, or a player failure, closes the field. Browser tab or window blur preserves the configured field and committed value.
The host imports are text_configure(pointer, length) -> u32 and
text_read(pointer, capacity) -> u32. Both use a fixed 48-byte little-endian
header followed by up to 256 UTF-8 bytes. The authoritative layout and flag
values live in contract.json under textInput. Configure takes revision zero;
the host assigns a fresh nonzero revision to each accepted configuration and
close. Browser edits carry that revision and a monotonically increasing sequence
to reject events from an earlier field or a repeated delivery. Validation failure
returns zero without changing the prior state or touching an output buffer.
Run mise run validate test:text-input for SDK lifecycle, native SDL event,
UTF-8 boundary and browser editing coverage.
Get started