Combo Box
A text input combined with a selectable list.
1use herogpui::components::combo_box::ComboBox;Usage
Values and options use 14px text with 20px lines. Section headers use 12px text with 16px lines and keep their own spacing. The popup flips near window edges and scrolls to keep options reachable in short windows.
1use herogpui::prelude::ComboBox;
2
3ComboBox::new(
4 self.combo_state.clone(),
5 language_items(),
6)
7.label("Language")
8.placeholder("Pick or type")
9.is_open(is_open)
10.on_open_change(bool_cb(cx.listener(|this, open: &bool, _, cx| {
11 this.combo_open = *open;
12 cx.notify();
13})))
14.on_selection_change(cx.listener(|this, _key: &SharedString, _, cx| {
15 this.combo_open = false;
16 cx.notify();
17}))Live HeroGPUI compiled to WebAssembly. Select any example without loading another WASM instance.
Anatomy
Rust types
ComboBox
Combo Box is assembled from these builders. The API reference lists each one.
Customization
Appearance builders and theme tokens Combo Box uses.
Styling
| Style | Description |
|---|---|
ComboBox text_size + line_height + section header styles | Text keeps its line height under surrounding styles. Section headers have 8px horizontal, 6px top and 4px bottom padding; selecting an option does not add font weight. |
flex_col + gap(px(4.)) + min_w(px(180.)) | Root field stack; matches the 4px gap but also enforces a local 180px minimum when not full width. |
is_invalid/error_message branch | Invalid fields hide the description slot so the error message can occupy the message position. |
Input::new + end_content(trigger) + Input focus styles | The input expands beside the trigger and uses the field focus treatment; delegates the inner field to Input. |
input_group.relative + Input end_content | Input and trigger positioning context; provides a relative group but not the full isolation contract. |
value_content(SelectionValue) + FIELD_TEXT + field.foreground | Selected-value row typography and placeholder treatment; supplies equivalent values only through value_content. |
trigger flex + size(px(20.)) + muted + end_content | Chevron trigger positioning and chrome; draws a 20px local hit surface through Input's end content. |
trigger.hover + interaction gates | Has hover and disabled interaction gates, but no separate trigger focus ring and no pressed opacity treatment. |
svg size(FIELD_ICON) + CHEVRON_DOWN | Uses a 16px chevron, but it does not rotate the icon from closed to open. |
scrollable_field_popover + max_h_full (plain) / Infer (virtual) + occlude + p(px(4.)) + overlay bg/shadow | Anchored to the field with an 8px gap, flipping to the side with more room when the preferred side cannot fit, keeping a 12px cross-axis viewport inset with the scroller capped to the available height; plain and virtual lists scroll within the available height and wheel input stays in the panel. Uses its own 4px panel inset and exact trigger width rather than upstream p-0 and min-width. |
anim::entering_zoom + Motion::LIST_IN | Matches fade, 95% start, duration and curve through metric animation, but has no placement-specific slide. |
panel p(px(4.)) + row px(8.)/py(6.) | Listbox inset; places 4px around the outer panel and 8px/6px on each row instead. |
row px(px(8.)) + indicator(render) | The local rows have 8px horizontal padding and draw a tick for multiple selections only. |
full_width(true) + root/input w_full | Full-width root and input group. |
API reference
Builders
| Builder | Type | Default | Description |
|---|---|---|---|
input_value( | value, | — | Current text in the input (controlled). |
default_input_value( | text | — | Initial text in the input (uncontrolled). |
on_input_change( | Fn( | — | Called when the input text changes. |
is_open( | bool | — | Controlled open state inherited from the ComboBox primitive. |
default_open( | bool | false | Initial open state for the uncontrolled popover. |
on_open_change( | Fn( | — | Called when focus, typing, the trigger, Escape, selection or outside press changes the open state. |
selection_mode( | SelectionMode | SelectionMode:: | Whether one or multiple options may be selected. |
selected_key( | key, | — | Controlled single-selection item key, with v3's null spelled as the empty string; writes the key's label through the input state and hands the key to the controlled selection. |
default_value( | Key | | — | Uncontrolled single-selection seed addressed by item key; default_value takes an ordered key list rather than a nullable key. |
on_selection_change( | Fn( | — | Reports the picked key; the null a cleared input or a committed custom value stands for is spelled through on_selection_change_all's empty slice — reported only when a previous selection existed — the port's rendering of the primitive's null. |
selected_keys( | Key | | — | Controlled selection as ordered item keys for both single and multiple modes; v3's null is the empty list, and a single-mode selection keeps the first listed key. |
new( | state, | — | Options displayed in the listbox as keyed PickerItems: the stable key the selection, disabledKeys and form value address, and the label the filtering, input text and rendering use. |
disabled_keys( | iterable of keys | — | Keys of the options that remain visible but cannot be selected; the state is per key, so one of two same-label items can be disabled alone. |
filter( | Fn( | — | Custom option matcher; the default is the language-sensitive useFilter base contains matcher, while the port's built-in matcher is case-insensitive substring and its filter callback is the direct companion seam. |
is_disabled( | bool | false | Disables the input, trigger, keyboard navigation and popover. |
is_read_only( | bool | false | Keeps the field readable and focusable while preventing edits and opening. |
is_required( | bool | false | Marks the field as required; carries the flag to its field and validation surface, without browser constraint-validation semantics. |
is_invalid( | bool | false | Marks the field invalid and switches the description slot to the error message. |
validate( | Fn( | — | Runs validation against the input text and surfaces an error, but does not pass the browser primitive's selectedKey-shaped validation value or full return union. |
validation_behavior( | ValidationBehavior | ValidationBehavior:: | Selects the local validation behavior carried by the field; native form blocking and ARIA attributes are not browser semantics in GPUI. |
name( | value) + form_field( | — | Names the local FormField whose live value follows formValue; the input keeps its implicit-Enter submission, but there is no automatic form boundary. |
form_value( | ComboBoxFormValue | ComboBoxFormValue:: | Chooses what a named field submits: the selected key(s) by default, the typed text under allowsCustomValue (pinned Components 1.20.0 forces text there). The serialization rides the local FormField; there is no browser form boundary. |
auto_focus( | bool | false | Focuses the input on its first render. |
allows_custom_value( | bool | false | Allows Enter to commit text that is not in the collection; the committed custom value keeps the typed text, carries a null selected key, and forces formValue to text as pinned does. |
allows_empty_collection( | bool | false | Keeps the popover open when filtering produces no rows. |
menu_trigger( | MenuTrigger | MenuTrigger:: | Chooses whether focus, input, or only an explicit trigger/key action opens the list. |
should_focus_wrap( | bool | false | Wraps keyboard navigation from the last option to the first and back. |
full_width( | bool | false | Makes the field and its input group fill the available width. |
variant( | FieldVariant | FieldVariant:: | Visual field treatment for the input group. |
value_content( | AnyElement | | — | Custom selected-value content; the local closure receives SelectionValue rather than the browser state object. |
placement( | Placement | Placement:: | Eight cardinal/start/end placements are available; RAC's full 22-value placement union is not. The panel anchors to the measured field with an 8px gap and flips when the preferred side cannot fit and the opposite side has more room. |
Parts
| Part | Description |
|---|---|
ComboBox | Field root owning input text, selection, filtering, validation, open state and the floating list. |
ComboBox | Input and trigger group; renders this as a built-in field composition rather than a child slot. |
ComboBox | Optional selected-value row with SelectionValue content; the browser placeholder and state object are not separate builders. |
ComboBox | Built-in chevron hit surface that toggles the list and focuses the input; it is not a separate browser Button element. |
ComboBox | Placement-aware floating list surface with outside-press and Escape dismissal; arbitrary popover children are not composable. |
States
| Builder | State | Description |
|---|---|---|
open_state + overlay_active + show_list | Open | The input group owns a live filtered list when the menu is open. |
InputState::focus_handle + MenuTrigger::Focus | Focused | Input focus drives the field's focused treatment and the default focus menu trigger. |
trigger.hover(default.hover()) | Hovered trigger | The trigger responds to hover, but the local hover fill is broader than v3's trigger text-color change. |
trigger_pressed capture token | Pressed trigger | The local trigger captures the press for dismissal ordering but does not reproduce v3's pressed opacity surface. |
is_disabled + interaction gates | Disabled | Input and trigger are inert and the popover cannot open; browser disabled attributes and root data state are unavailable. |
is_read_only + interaction gates | Read only | The input remains readable while edits, opening and selection changes are gated. |
is_invalid/error_message + Input invalid | Invalid | Error content replaces the description and the field is styled invalid; GPUI has no matching attributes. |
is_required + form_field | Required | The required flag reaches the local field and validator, without browser required/form semantics. |
filter/default substring + max_items | Filtered | Rows are limited by the current input and optional custom matcher; the locale-sensitive useFilter default is approximated by case-insensitive substring matching. |
allows_empty_collection + empty message | Empty collection | AllowsEmptyCollection keeps the panel open and draws an explicit no-match or custom-value message. |
disabled_keys + disabled opacity gate | Disabled option | Disabled labels remain visible and cannot be activated, with local muted opacity in place of browser collection semantics. |
selected_keys + indicator(render) | Selected option | Selected labels are tracked and multiple selection can draw an indicator; generic ListBox item data attributes are not emitted. |
selection_mode(Multiple) + on_selection_change_all | Multiple selection | Rows toggle membership without closing and ComboBox.Value can receive the selected labels. |
allows_custom_value + Enter activation | Custom value | Enter commits unmatched input and reports it through the local selection callback. |
menu_trigger(MenuTrigger) | Menu trigger | Focus, input and manual opening modes match the documented trigger choices, including arrow-key opening. |
should_focus_wrap + list_nav::resolve | Focus wrap | Keyboard navigation can wrap between the first and last enabled rows. |
anim::entering_zoom + Motion::LIST_IN | Entering | The list fades and grows from 95% over 150ms with the list curve; placement-specific slide translations are not reproduced. |