6.7k

Input

Single-line text entry. input and text-field are the same foundation under two names: text and placeholder bind from the model, on-input names a Msg variant that receives every edit as a text-input event (the TextInputEvent union from @native-sdk/core/text in a TypeScript core; canvas.TextInputEvent in a Zig core), and on-submit dispatches on enter. search-field is the same control styled for search.

input
Text inputs rendered by the engine (light theme)
placeholder, bound text, and disabled states

Markup

<column gap="12" width="280">
  <input placeholder="Email address" text="{email}" on-input="email_edited" on-submit="subscribe" />
  <text-field placeholder="Project name" text="{project_name}" on-input="name_edited" />
  <input placeholder="Disabled" disabled="true" />
</column>

The core side is a text field the control renders and one arm reducing each edit over it — Native UI § Messages walks the whole contract:

import { type TextInputEvent } from "@native-sdk/core/text";
// the same module ships the full byte-splice reducer (applyTextInputEvent)
// for update to fold each edit over the model's draft bytes

export type Msg =
  | { readonly kind: "subscribe" }
  | { readonly kind: "email_edited"; readonly edit: TextInputEvent };
email_buffer: canvas.TextBuffer(128) = .{},
pub fn email(model: *const Model) []const u8 {
    return model.email_buffer.text();
}
// in update:
.email_edited => |edit| model.email_buffer.apply(edit),

Search field

search-field renders the search affordance but binds exactly like an input; pair it with a model-filtered list. Whenever the field holds text it also shows a built-in clear affordance — a small x inside its trailing edge — and pressing it (or pressing Escape while focused) clears through the standard text-edit path, so the on-input handler receives the clear like any other edit and a model-owned buffer empties with it. No attribute enables or disables this; searchable fields simply carry it. For text entry that opens a menu of suggestions, see combobox.

search-field
A search field rendered by the engine (light theme)
<search-field width="280" placeholder="Search notes" text="{query}" on-input="query_edited" />

Programmatic construction (Zig)

In a Zig view, the canvas.Ui builder constructs the same tree programmatically. on_input takes a comptime message constructor: Ui.inputMsg(.tag) builds Msg{ .tag = edit } for each canvas.TextInputEvent.

ui.column(.{ .gap = 12, .width = 280 }, .{
    ui.el(.input, .{
        .placeholder = "Email address",
        .text = model.email,
        .on_input = Ui.inputMsg(.email_edited),
        .on_submit = .subscribe,
    }, .{}),
    ui.textField(.{ .placeholder = "Project name", .text = model.project_name, .on_input = Ui.inputMsg(.name_edited) }),
    ui.el(.input, .{ .placeholder = "Disabled", .disabled = true }, .{}),
})

The search field builds the same way:

ui.el(.search_field, .{
    .width = 280,
    .placeholder = "Search notes",
    .text = model.query,
    .on_input = Ui.inputMsg(.query_edited),
}, .{})

Attributes

AttributeDescription
textText value for text-bearing elements; a literal or one {binding}.
placeholderHint text shown while a text entry is empty.
valueValue for slider/progress/text entry; a literal or one {binding}.
disabledDisables the control; true/false or a {binding}.
autofocusFocusable controls only: moves keyboard focus to the element when it mounts or when the value turns on (edge-triggered - holding it true never re-steals focus). The TEA way to focus an editor on create.
on-inputNames a Msg variant with canvas.TextInputEvent payload; delivers each text edit.
on-submitDispatch a Msg on submit: tag or tag:{payload}. Enter in a text field, primary+enter in a textarea; on a list-item, plain Enter dispatches it as the row's primary action while Space keeps the row's select (on-press).
on-changeDispatch a Msg on change: tag or tag:{payload}. Hit-target elements only (slider, ...).