Select
select is a trigger-only primitive — it takes no options attribute. Its content is the current value (placeholder shows while empty) and on-press opens. The options are composed as an anchored dropdown-menu of menu-items beside the trigger in a stack, rendered under an if — the model owns the open flag, and on-dismiss clears it when Escape, Tab, or a click outside closes the surface. In the open menu the selected option wears a trailing checkmark, and the row under the keyboard or pointer carries a full-row highlight — the two are independent, so arrowing away from the committed option never moves its mark. For a trigger the user types into, see combobox.

Markup
The full pattern: trigger and menu are siblings in a stack, the menu floats with anchor="below" (anchor-alignment="stretch" widens it to the trigger, the select-menu look), and every path back to closed goes through the model — picking dispatches pick_env and on-dismiss dispatches close_env_picker, both of which clear env_picker_open in update.
The keyboard rides the same composition with no extra wiring: on the focused trigger, Enter, Space, or an arrow key presses it (the model-owned open); with the menu mounted, Down/Up move into and walk the options (entering at the selected one) and Home/End jump to the edges — walking only moves the highlight, it never dispatches a pick. Enter or a click commits the highlighted option (one on-press, then the model closes); Escape, Tab, or a click outside dismisses through on-dismiss with the committed value untouched — focus returns to the trigger either way.
<stack width="240">
<select placeholder="Choose environment" on-press="toggle_env_picker">{envName}</select>
<if test="{env_picker_open}">
<dropdown-menu anchor="below" anchor-alignment="stretch" on-dismiss="close_env_picker">
<for each="environments" key="id" as="e">
<menu-item selected="{e.id == env_id}" on-press="pick_env:{e.id}">{e.name}</menu-item>
</for>
</dropdown-menu>
</if>
</stack>Zig builder
ui.stack(.{ .width = 240 }, .{
ui.el(.select, .{
.text = model.envName(),
.placeholder = "Choose environment",
.on_press = .toggle_env_picker,
}, .{}),
if (model.env_picker_open) ui.el(.dropdown_menu, .{
.anchor = .below,
.anchor_alignment = .stretch,
.on_dismiss = .close_env_picker,
}, .{
ui.el(.menu_item, .{ .text = "Production", .selected = model.env == .production, .on_press = .{ .pick_env = .production } }, .{}),
ui.el(.menu_item, .{ .text = "Staging", .selected = model.env == .staging, .on_press = .{ .pick_env = .staging } }, .{}),
ui.el(.menu_item, .{ .text = "Development", .selected = model.env == .development, .on_press = .{ .pick_env = .development } }, .{}),
}) else ui.spacer(0),
})Attributes
| Attribute | Description |
|---|---|
text | Text value for text-bearing elements; a literal or one {binding}. |
placeholder | Hint text shown while a text entry is empty. |
disabled | Disables the control; true/false or a {binding}. |
on-press | Dispatch 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-dismiss | Dismissible 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. |
