ComboBox
Stable single selection, overlay popup, keyboard navigation and optional text filtering.
ComboBox binds an Option<T> where T: Clone + PartialEq. Options have separate
IDs, values and labels. IDs must be unique and stable across insertion, removal
and reordering. Labels can repeat and are rendered literally, including ##.
use zaxis::{ComboBox, ComboBoxOption};
let options = [
ComboBoxOption::new("eu", "europe", "Europe / Европа"),
ComboBoxOption::new("us", "america", "America"),
ComboBoxOption::new("offline", "offline", "Unavailable").enabled(false),
];
let mut selected = Some("europe");
let response = ui.add(ComboBox::new(&mut selected, &options)
.id_source("region")
.label("region")
.filterable(true)
.width(240.0)
.visible_rows(5)
.animation_duration(std::time::Duration::from_millis(160)));
if response.changed() {
// Persist selected or update the application.
}ui.combo_box(&mut selected, &options) is the short form without a caption or
filter. For a plain enum or number, skip the option list:
ui.combo_box_values(&mut mode, [(Mode::Fast, "Fast"), (Mode::Exact, "Exact")]) binds
the value directly and derives option ids from the values (T: Hash), like tab_bar.
ComboBox::from_pairs(&mut Option<T>, pairs) is the builder form with every option. Use id_source or Ui::push_id for dynamic/repeated controls.
default_open sets the initial state; interaction is retained by ID afterward.
changed() is true only when an enabled option changes the bound value.
Selecting the current value closes the list without reporting a change.
Disabled controls preserve layout and values; disabled options are visible but
cannot be selected with the pointer or keyboard. An empty list cannot open.
If a selected value disappears, the model is preserved and placeholder is
shown until the application or user supplies a valid selection. Updating an
open list preserves the active option by ID; a removed or disabled active option
falls back to the first enabled match. Distinct values identify distinct selections.
Keyboard and filtering
Tab focuses the trigger. Up/Down, Enter or Space opens the list, initially
revealing the selected enabled option. Up/Down skips disabled options; Home/End
selects the first/last active candidate; Page Up/Down moves by visible_rows.
Enter selects the active option. Escape cancels and returns focus to the trigger.
Tab closes the popup and continues normal focus traversal. A combo box built inside a
Popup opens its list as a child of it: choosing, Escape and Tab close the
list and leave the parent open. Pointer selection
also returns focus to the trigger. Navigation scrolls the active row into view.
filterable(true) uses the existing TextEdit, including Unicode editing,
clipboard and IME. Opening focuses an empty filter. Matching is a case insensitive
substring of the label. Up/Down and Enter operate the results; Space, Home/End
and Left/Right retain their text editing behavior. No matches leaves the filter
editable without changing the selection. The query resets on the next opening.
Appearance and layering
Style::combo_box contains ComboBoxStyle: the palette, compact 22-pixel trigger,
20-pixel rows with 2-pixel gaps, caption spacing, trigger and popup padding,
4-pixel rounding, thin idle/hover borders, and the 160 ms animation duration.
The default list shows up to five row heights and reuses ScrollArea with
virtual rows. The chevron rotates geometrically; the selected check is also
vector geometry. Unselected labels use the muted color.
Use .style(custom_style) for a complete per-widget override. Builders also
override width, trigger_height, row_height, row_gap, label_gap,
padding, popup_padding, rounding, font_size, visible_rows and
animation_duration. These overrides are resolved against Style::combo_box
each pass. Style::motion.reduced_motion removes transitions.
The shared Popup places the list above every window and escapes the parent's clip. It fits horizontally to the viewport, opens upward when the bottom lacks room, and limits its height to available space. Outside presses close it and consume the gesture. Closing removes input regions immediately; the remaining visual transition owns no hits. Rapid toggles retarget the same animation channel from its current value. Settled or hidden animation schedules no redraws; a focused filter can still schedule its normal caret blink.
Option IDs, enabled state and label content are compared against a retained snapshot while the popup is visible. Duplicate-ID validation and filter result rebuilding run only after changes. Folded labels survive reordering and are updated for in-place label edits. Closed controls do not scan/filter the entire list on each pass. ComboBox benchmarks measure cached, animated, keyboard, scroll, filter and live-update paths.
Run cargo run --example combo_box for a long list, repeated labels, disabled
options, delayed option updates, overlapping panels, a clipped parent and a
draggable viewport-edge panel. -- --open opens the searchable list initially;
-- --smoke-test renders the popup through the native GPU runner and exits.
Accessibility
Role ComboBox, named by its label; the value is the label of the selected option. It
publishes its expanded state and accepts Click, Expand, Collapse and SetValue with
the label of an enabled option. While open, the list is a ListBox of ListBoxOption
nodes with their selected state and position in the whole set; an option accepts Click.
The node that holds focus (the trigger or the filter field) names the highlighted option
as its active descendant. Options that were not built have no node.
See Accessibility.