7.7k

Combobox

combobox is a trigger-only primitive like select, but the trigger is a text entry with a menu affordance: on-input names a Msg variant that receives every edit as a text-input event (canvas.TextInputEvent in a Zig core; the TextInputEvent union from @native-sdk/core/text in a TypeScript core), and the model filters the options as the user types. The options themselves are composed the same way as the select's — an anchored dropdown-menu of menu-items beside the trigger in a stack, rendered under an if, with on-dismiss clearing the model's open flag when Escape or a click outside closes the surface.

Enter submits when on-submit is bound; otherwise Enter opens the picker. Space and the open-arrow keys (Down/Up) always open it. Once focus moves into the open menu, Enter selects the focused menu-item as usual.

combobox
A combobox rendered by the engine (light theme)
a combobox trigger with its search placeholder

Markup

The model owns the query and the open flag; the for source is the model-filtered match list, so the menu narrows as the user types.

<stack width="240">
  <combobox placeholder="Search frameworks" text="{frameworkQuery}" on-input="framework_edited" on-submit="commit_framework_query" on-press="open_framework_menu" />
  <if test="{frameworkMenuOpen}">
    <dropdown-menu anchor="below" anchor-alignment="stretch" on-dismiss="close_framework_menu">
      <for each="matchingFrameworks" key="id" as="f">
        <menu-item on-press="pick_framework:{f.id}">{f.name}</menu-item>
      </for>
    </dropdown-menu>
  </if>
</stack>

In the primary TypeScript core, the submit arm commits the current model-owned query. The open and submit messages remain separate, so Enter can commit without toggling the picker:

export type Msg =
  | { readonly kind: "framework_edited"; readonly edit: TextInputEvent }
  | { readonly kind: "open_framework_menu" }
  | { readonly kind: "close_framework_menu" }
  | { readonly kind: "commit_framework_query" };

case "commit_framework_query":
  return { ...model, committedFrameworkQuery: model.frameworkQuery, frameworkMenuOpen: false };

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.stack(.{ .width = 240 }, .{
    ui.el(.combobox, .{
        .placeholder = "Search frameworks",
        .text = model.framework_query,
        .on_input = Ui.inputMsg(.framework_edited),
        .on_submit = .commit_framework_query,
        .on_press = .open_framework_menu,
    }, .{}),
    if (model.framework_menu_open) ui.el(.dropdown_menu, .{
        .anchor = .below,
        .anchor_alignment = .stretch,
        .on_dismiss = .close_framework_menu,
    }, .{
        ui.el(.menu_item, .{ .text = "Native SDK", .on_press = .{ .pick_framework = 0 } }, .{}),
        ui.el(.menu_item, .{ .text = "Canvas", .on_press = .{ .pick_framework = 1 } }, .{}),
    }) else ui.spacer(0),
})

Attributes

AttributeDescription
textText value for text-bearing elements; a literal or one {binding}.
placeholderHint text shown while a text entry is empty.
disabledDisables the control; true/false or a {binding}.
on-pressDispatch a Msg on press: tag or tag:{payload}. Legal on any element — a bound press handler makes it pressable, and presses on plain text/icons inside it fall through to it (dragging still selects text).
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-dismissDismissible surfaces only (dialog, drawer, sheet, dropdown-menu): Msg dispatched when Escape or a click outside dismisses the surface, so the MODEL owns the close (clear the open flag in update). The engine hides the surface immediately as an optimistic echo; the source tree wins on the next rebuild.