Combo Box
A text input combined with a selectable list.
use herogpui::components::combo_box::ComboBox;ComboBox on docs.rsSource at v0.14.0
Usage
Showing Usage, live: HeroGPUI compiled to WebAssembly. The canvas has no accessibility tree; each example's description and code below carry the same information.
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. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-usage", "", cx),
language_items(),
)
.label("Language")
.placeholder("Pick or type")
.is_open(self.demo_overlay("cb-usage-open"))
// `radius` is the detached panel's corner; the trigger is
// the inner input's box and keeps the field chrome's own.
.radius(px(8.))
.on_open_change(cx.listener(|this, open: &bool, _, cx| {
this.set_demo_flag("cb-usage-open", *open);
cx.notify();
}))
.on_selection_change(cx.listener(|this, _key: &SharedString, _, cx| {
this.set_demo_flag("cb-usage-open", false);
cx.notify();
}))
.into_any_element()])Also calls languages, language_items (included when you copy).
The list can enter above its field; the placement translation follows the physical side resolved by the viewport positioner. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem, Placement};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-placement-top", "", cx),
language_items(),
)
.label("Language")
.placeholder("Pick or type")
.placement(Placement::Top)
.default_open(true)
.into_any_element()])Also calls languages, language_items (included when you copy).
The trigger forwards `height`, `padding_x` and `is_bare` to its input, and `row_padding_x` / `row_padding_y` size the rows. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ActiveTheme, ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-custom-box", "", cx),
language_items(),
)
.label("Compact")
.placeholder("Pick one")
.height(px(28.))
.padding_x(px(4.))
.is_bare(true)
.row_padding_x(px(16.))
.row_padding_y(px(2.))
.row_hover_bg(cx.colors().accent.soft())
.font_family(MONO_FONT)
.row_font_family(MONO_FONT)
.into_any_element()])Also calls languages, language_items, MONO_FONT (included when you copy).
`min_width` replaces the 180px floor the root keeps while it is not full-width, for a compact filter; a full-width combo box has no floor. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-min-width", "", cx),
language_items(),
)
.label("Filter")
.min_width(px(120.))
.into_any_element()])Also calls languages, language_items (included when you copy).
`row_height` makes the list geometry computable, so a uniform `VirtualList` builds only the rows in view. A thousand options, forty pixels each. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
gpui::div()
.w(px(256.))
.flex()
.flex_col()
.child(ComboBox::new(
self.demo_text("cb-virtual", "", cx),
virtual_picker_items(),
)
.label("User")
.placeholder("Select a user")
.max_items(1000)
.row_height(px(40.)))
.into_any_element(),
])Also calls virtual_user, virtual_picker_items (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![gpui::div()
.w(px(400.))
.child(
ComboBox::new(
self.demo_text("cb-full", "", cx),
language_items(),
)
.label("Language")
.placeholder("Pick or type")
.full_width(true),
)
.into_any_element()])Also calls languages, language_items (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-desc", "", cx),
language_items(),
)
.label("Language")
.placeholder("Select a language")
.description("Pick from the list or type your own")
.into_any_element()])Also calls languages, language_items (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-required", "", cx),
language_items(),
)
.label("Language")
.placeholder("Select a language")
.is_required(true)
.into_any_element()])Also calls languages, language_items (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-disabled", "Rust", cx),
language_items(),
)
.label("Language")
.placeholder("Select a language")
.is_disabled(true)
.into_any_element()])Also calls languages, language_items (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-readonly", "Rust", cx),
language_items(),
)
.label("Language")
.placeholder("Select a language")
.is_read_only(true)
.into_any_element()])Also calls languages, language_items (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, FieldVariant, PickerItem, Surface};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![Surface::new()
.padding(px(24.))
.child(
ComboBox::new(self.demo_text("cb-surface", "", cx), language_items())
.label("Language")
.placeholder("Select a language")
.variant(FieldVariant::Secondary),
)
.into_any_element()])Also calls languages, language_items (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-disabled-opts", "", cx),
language_items(),
)
.label("Language")
.placeholder("Select a language")
.disabled_keys([SharedString::from("go")])
.default_open(true)
.into_any_element()])Also calls languages, language_items (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-sections", "", cx),
vec![
PickerItem::new("rust", "Rust"),
PickerItem::new("go", "Go"),
PickerItem::new("typescript", "TypeScript"),
PickerItem::new("python", "Python"),
],
)
.label("Language")
.placeholder("Select a language")
.section_before("rust", "Systems")
.section_before("typescript", "Scripting")
.default_open(true)
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ActiveTheme, ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, SharedString, px};
use herogpui::gpui;
let cb_picked = self.demo_text_value("cb-picked");
let cb_typed = self.demo_text_value("cb-typed");
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
ComboBox::new(self.demo_text("cb-controlled", "", cx), language_items())
.label("Language")
.placeholder("Select a language")
// `selectedKey` is the controlled selection key
// (empty string is v3's `null`); the input shows
// that key's label and `inputValue` holds the
// typed text. The pick reports the key, which is
// what the line below displays.
.selected_key(cb_picked.clone())
.input_value(cb_typed.clone())
.on_selection_change(cx.listener(
|this, key: &SharedString, _, cx| {
this.set_demo_text_value("cb-picked", key.to_string());
cx.notify();
},
))
.into_any_element(),
para(
&if cb_picked.is_empty() {
"Nothing picked yet".to_owned()
} else {
format!("Picked: {cb_picked}")
},
cx,
),
])Also calls languages, language_items, para (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ActiveTheme, ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, SharedString, px};
use herogpui::gpui;
let cb_typed = self.demo_text_value("cb-typed");
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
ComboBox::new(self.demo_text("cb-input", "", cx), language_items())
.label("Language")
.placeholder("Select a language")
.on_input_change(cx.listener(|this, text: &str, _, cx| {
this.set_demo_text_value("cb-typed", text.to_owned());
cx.notify();
}))
.into_any_element(),
para(&format!("Typed: {cb_typed}"), cx),
])Also calls languages, language_items, para (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem, SelectionMode};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
let cb_multi = self.demo_selection("cb-multi");
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-ctl-sel", "", cx),
language_items(),
)
.label("Languages")
.placeholder("Select languages")
.selection_mode(SelectionMode::Multiple)
.selected_keys(cb_multi.iter().cloned())
.on_selection_change_all(cx.listener(|this, keys: &[SharedString], _, cx| {
this.set_demo_selection("cb-multi", keys.to_vec());
cx.notify();
}))
.into_any_element()])Also calls languages, language_items (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem, SelectionMode};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-multi-sel", "", cx),
language_items(),
)
.label("Languages")
.placeholder("Select languages")
.selection_mode(SelectionMode::Multiple)
.default_open(true)
.into_any_element()])Also calls languages, language_items (included when you copy).
Pick a language with the pointer or keyboard, or clear the input. The value below follows the controlled selection. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
let cb_value = self
.demo_selections
.get("cb-value")
.cloned()
.unwrap_or_else(|| vec![SharedString::from("rust")]);
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-value", "Rust", cx),
language_items(),
)
.label("Language")
.placeholder("Select a language")
.selected_keys(cb_value.iter().cloned())
.on_selection_change_all(cx.listener(
|this, keys: &[SharedString], _, cx| {
this.set_demo_selection("cb-value", keys.to_vec());
cx.notify();
},
))
.value_content(|value| {
if value.is_placeholder {
gpui::div()
.text_size(px(14.))
.child("No language selected")
.into_any_element()
} else {
value.default_children
}
})
.into_any_element()])Also calls languages, language_items (included when you copy).
The default `ComboBox.Value` render-prop content follows wrap-break-word contract and grows for long selected labels. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
gpui::div()
.w(px(260.))
.child(
ComboBox::new(
self.demo_text("cb-long-value", "", cx),
vec![
PickerItem::new(
"long",
"A project name that is intentionally long enough to wrap",
),
PickerItem::new("short", "Short option"),
],
)
.label("Project")
.placeholder("Choose one")
.default_value(["long"])
.value_content(|value| value.default_children),
)
.into_any_element(),
])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-default-key", "TypeScript", cx),
language_items(),
)
.label("Language")
.placeholder("Search languages...")
.default_value(["typescript"])
.into_any_element()])Also calls languages, language_items (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-custom", "Zig", cx),
language_items(),
)
.label("Language")
.placeholder("Pick or type")
.allows_custom_value(true)
.into_any_element()])Also calls languages, language_items (included when you copy).
The list is filled from a request. The spinner beside the field is what says one is in flight; the options are the caller's own data. `allows_empty_collection` keeps the panel up with its empty state while a query has no matches instead of collapsing it. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, Filter, PickerItem, Sensitivity, Spinner, SpinnerSize};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![
ComboBox::new(self.demo_text("cb-async", "", cx), language_items())
.label("Language")
.placeholder("Select a language")
.allows_empty_collection(true)
// v3 pairs the flag with async loading: type a
// query nothing matches and the panel stays up
// with "No matching options" instead of closing.
.filter(|item, input| {
Filter::new(Sensitivity::Base).contains(item, input)
})
.into_any_element(),
Spinner::new("combo-box-async-spinner")
.size(SpinnerSize::Sm)
.into_any_element(),
])
.into_any_element(),
])Also calls languages, language_items (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem, SelectionMode};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-indicator", "", cx),
language_items(),
)
.label("Languages")
.placeholder("Select languages")
.selection_mode(SelectionMode::Multiple)
.default_open(true)
.indicator(|is_selected| {
gpui::div()
.text_size(px(12.))
.child(if is_selected { "\u{2714}" } else { "" })
.into_any_element()
})
.into_any_element()])Also calls languages, language_items (included when you copy).
`defaultFilter` here is `useFilter`'s `startsWith`, so it matches on the start of the name only. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, Filter, PickerItem, Sensitivity};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
ComboBox::new(self.demo_text("cb-filter", "", cx), language_items())
.label("Language")
.placeholder("Select a language")
.filter(|item, input| {
Filter::new(Sensitivity::Base).starts_with(item, input)
})
.default_open(true)
.into_any_element(),
])Also calls languages, language_items (included when you copy).
Also calls languages, language_items (included when you copy).
Items are keyed `PickerItem`s: the selection is the item's key while the input shows its label, and `form_value` decides what a named field submits. The default (`ComboBoxFormValue::Key`) submits the picked key -- save with a pick and the submitted value is `language=rust`, not `Rust` -- and `allows_custom_value` keeps the typed text. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ActiveTheme, Button, ComboBox, ComboBoxFormValue, Form, FormData, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
{
let combo = ComboBox::new(
self.demo_text("cb-form", "", cx),
language_items(),
)
.label("Language")
.placeholder("Select a language")
.name("language")
.is_required(true);
Form::new()
.field(combo.form_field().expect("named combo field"))
.on_submit(cx.listener(|this, data: &FormData, _, cx| {
this.set_demo_text_value(
"cb-form-submitted",
data.text("language").unwrap_or_default().to_string(),
);
cx.notify();
}))
.child(combo)
.child(Button::new("combo-box-form-submit").label("Save"))
.into_any_element()
},
para(
&if self.demo_text_value("cb-form-submitted").is_empty() {
"Nothing submitted yet".to_owned()
} else {
format!(
"Submitted: {}",
self.demo_text_value("cb-form-submitted")
)
},
cx,
),
ComboBox::new(
self.demo_text("cb-form-custom", "", cx),
language_items(),
)
.label("Language")
.placeholder("Pick or type")
.name("custom-language")
.allows_custom_value(true)
.form_value(ComboBoxFormValue::Text)
.into_any_element(),
])Also calls languages, language_items, para (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem, ValidationBehavior};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
ComboBox::new(self.demo_text("cb-vb-native", "", cx), language_items())
.label("Language")
.placeholder("Select a language")
.is_required(true)
.validation_behavior(ValidationBehavior::Native)
.into_any_element(),
ComboBox::new(self.demo_text("cb-vb-allow", "", cx), language_items())
.label("Language")
.placeholder("Select a language")
.is_required(true)
.validation_behavior(ValidationBehavior::Allow)
.into_any_element(),
])Also calls languages, language_items (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-validate", "Zig", cx),
language_items(),
)
.label("Language")
.allows_custom_value(true)
.validate(|value| {
(!value.is_empty() && !languages().iter().any(|l| l == value))
.then(|| "Pick one of the listed languages".into())
})
.into_any_element()])Also calls languages, language_items (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-custom-value", "", cx),
language_items(),
)
.label("Language")
.placeholder("Pick or type")
.allows_custom_value(true)
.is_open(self.demo_overlay("cb-custom-open"))
.on_open_change(cx.listener(|this, open: &bool, _, cx| {
this.set_demo_flag("cb-custom-open", *open);
cx.notify();
}))
.into_any_element()])Also calls languages, language_items (included when you copy).
The plainest combo box: a labeled input over a two-item list, uncontrolled, with the trigger opening the same filtered list. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ComboBox, PickerItem};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![ComboBox::new(
self.demo_text("cb-basic", "", cx),
vec![
PickerItem::new("cat", "Cat"),
PickerItem::new("dog", "Dog"),
],
)
.label("Favorite Animal")
.placeholder("Search animals...")
.into_any_element()])Helpers used by these examples
Plain functions over the public API, defined once here and copied with each example that calls them.
fn languages() -> Vec<SharedString> {
vec![
"Rust".into(),
"TypeScript".into(),
"Python".into(),
"Go".into(),
"Swift".into(),
"Kotlin".into(),
]
}/// `languages()` as keyed items, for the Autocomplete and ComboBox demos: the
/// slug is the stable key the selection, `disabledKeys` and the form value
/// address, the display name the label the filtering, input text and
/// rendering use.
fn language_items() -> Vec<PickerItem> {
languages()
.into_iter()
.map(|label| PickerItem::new(label.to_lowercase(), label))
.collect()
}const MONO_FONT: &str = if cfg!(target_arch = "wasm32") {
// The web shell bundles JetBrains Mono; the literal family "monospace"
// resolves to nothing there and panics text resolution.
"JetBrains Mono"
} else if cfg!(target_os = "macos") {
"Menlo"
} else {
"Consolas"
};/// The nth of the thousand users v3's virtualization examples list, as
/// `(name, email)`. Same two twenty-name lists, so the rows read the same.
fn virtual_user(i: usize) -> (String, String) {
pub(super) const FIRST: [&str; 20] = [
"Emma",
"Liam",
"Olivia",
"Noah",
"Ava",
"James",
"Sophia",
"Oliver",
"Isabella",
"Lucas",
"Mia",
"Ethan",
"Charlotte",
"Mason",
"Amelia",
"Logan",
"Harper",
"Alexander",
"Ella",
"Benjamin",
];
pub(super) const LAST: [&str; 20] = [
"Smith",
"Johnson",
"Williams",
"Brown",
"Jones",
"Garcia",
"Miller",
"Davis",
"Rodriguez",
"Martinez",
"Anderson",
"Taylor",
"Thomas",
"Jackson",
"White",
"Harris",
"Clark",
"Lewis",
"Robinson",
"Walker",
];
let first = FIRST[i % FIRST.len()];
let last = LAST[(i / FIRST.len()) % LAST.len()];
(
format!("{first} {last}"),
format!("{}.{}@acme.com", first.to_lowercase(), last.to_lowercase()),
)
}/// The same thousand names as keyed picker items: the key is the stable
/// `user-N` id, the label the visible name.
fn virtual_picker_items() -> Vec<PickerItem> {
(0..1000)
.map(|i| PickerItem::new(format!("combo-box-{i}"), virtual_user(i).0))
.collect()
}fn para(text: &str, cx: &App) -> gpui::AnyElement {
let colors = cx.colors();
gpui::div()
.text_size(px(14.5))
.line_height(px(24.))
.text_color(colors.foreground)
.max_w(px(720.))
.child(text.to_owned())
.into_any_element()
}Anatomy
Rust types
Combo Box is assembled from these builders. The API reference lists each one.
Customization
Shared appearance belongs on ThemeBuilder::components and .recipe("name"). Typed builders change how Combo Box looks at one call site. Every builder also carries sx for placement, width and flex: it takes GPUI's own styling methods and refines them over the component's root after the theme, so an instance override wins. Behaviour builders live in the API reference below.
Styling
| Method | Values | Description |
|---|---|---|
full_width(bool) | true | false | Makes the field and its input group fill the available width. |
variant(FieldVariant) | FieldVariant::Primary | FieldVariant::Secondary | Visual field treatment for the input group. |
placement(Placement) | Placement::Bottom | Placement::BottomLeft | Placement::BottomRight | Placement::BottomStart | Placement::BottomEnd | Placement::Top | Placement::TopLeft | Placement::TopRight | Placement::TopStart | Placement::TopEnd | Placement::Left | Placement::LeftTop | Placement::LeftBottom | Placement::Start | Placement::StartTop | Placement::StartBottom | Placement::Right | Placement::RightTop | Placement::RightBottom | Placement::End | Placement::EndTop | Placement::EndBottom | The full 22-value union renders: physical and aligned spellings plus the logical start/end aliases, which share their left/right side's pixels in this LTR-only port. 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. |
// The one slot for caller-owned low-level styling: GPUI's
// styling methods, refined over the root element last.
.sx(|el| {
el.bg(gpui::rgba(0xffa500ff)) // background
.text_color(gpui::rgba(0x000000ff)) // text
.w(gpui::px(13.)) // size
.h(gpui::px(12.))
})API reference
Against HeroUI v3.2.6: 36 entries ported, 28 partial, 17 not ported. Partial rows are marked in the tables; entries not ported are listed under each table with the reason.
Builders
| Builder | Values | Default | Description |
|---|---|---|---|
input_value( | string | โ | Current text in the input (controlled). |
default_input_value( | string | โ | Initial text in the input (uncontrolled). |
on_input_change( | Fn( | โ | Called when the input text changes. |
is_open( | true | | โ | Controlled open state inherited from the ComboBox primitive. Independent examples keep separate open owners and feed back on_open_change. |
default_open( | true | | 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(Partial | 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(Partial | 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. |
on_selection_change_all( | Fn( | โ | Reports the complete Key | null | Key[] union: changed single picks by pointer, Enter or Tab, and every multiple-selection change. Re-picking the current single key is silent. Clearing or committing a custom value reports an empty slice only when a previous selection existed. |
new(Partial | iterable of T | โ | 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(Partial | 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( | true | | false | Disables the input, trigger, keyboard navigation and popover. |
is_read_only( | true | | false | Keeps the field readable and focusable while preventing edits and opening. |
is_required(Partial | true | | false | Marks the field as required; carries the flag to its field and validation surface, without browser constraint-validation semantics. |
is_invalid( | true | | false | Marks the field invalid and switches the description slot to the error message. |
validate(Partial | 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(Partial | ValidationBehavior:: | ValidationBehavior:: | Selects the local validation behavior carried by the field; native form blocking and ARIA attributes are not browser semantics in GPUI. |
name(Partial | string | โ | 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.21.1 forces text there). The serialization rides the local FormField; there is no browser form boundary. |
auto_focus( | true | | false | Focuses the input on its first render. |
allows_custom_value( | true | | 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( | true | | 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( | true | | false | Wraps keyboard navigation from the last option to the first and back. |
full_width( | true | | false | Makes the field and its input group fill the available width. |
focus_ring( | true | | true | GPUI extension: hides only the editable trigger's visual focus ring while retaining focus, editing and popup behavior. |
variant( | FieldVariant:: | FieldVariant:: | Visual field treatment for the input group. |
value_content(Partial | AnyElement | | โ | Custom selected-value content; the local closure receives SelectionValue rather than the browser state object. |
placement( | Placement:: | Placement:: | The full 22-value union renders: physical and aligned spellings plus the logical start/end aliases, which share their left/right side's pixels in this LTR-only port. 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. |
Not ported16 HeroUI entries not ported
| HeroUI v3 | Why it is not ported |
|---|---|
ComboBox.form | Associates the input with a browser form by id; GPUI has no form owner. |
ComboBox.autoComplete | Browser autocomplete hint for the input. |
ComboBox.className | Additional classes. |
ComboBox.render | Replaces the root element with a custom render function; GPUI does not expose a render seam. |
ComboBox.InputGroup.className | Additional classes for the input and trigger group. |
ComboBox.Value.placeholder | Fallback content when no item is selected; there is no separate placeholder builder on the port's value closure. |
ComboBox.Value.className | Static or state-derived classes for the selected-value part. |
ComboBox.Trigger.className | Additional classes for the popover trigger button. |
ComboBox.Trigger.children | Custom trigger content; draws a fixed chevron hit surface. |
ComboBox.Popover.className | Additional classes for the popover surface. |
ComboBoxRenderProps.isOpen | Open state supplied to the root render function. |
ComboBoxRenderProps.isDisabled | Disabled state supplied to the root render function. |
ComboBoxRenderProps.isInvalid | Resolved invalid state supplied to the root render function. |
ComboBoxRenderProps.isRequired | Required state supplied to the root render function. |
ComboBoxRenderProps.isReadOnly | Read-only state supplied to the root render function. |
ComboBoxValueRenderProps.state | /Stately state object for the value render function; no browser state entity is exposed. |
Parts
| Part | Description |
|---|---|
ComboBox | Field root owning input text, selection, filtering, validation, open state and the floating list. Simultaneous ComboBoxes need distinct InputState owners for internal identity. |
ComboBoxPartial | Input and trigger group; renders this as a built-in field composition rather than a child slot. |
ComboBoxPartial | Optional selected-value row with SelectionValue content; the browser placeholder and state object are not separate builders. |
ComboBoxPartial | Built-in chevron hit surface that toggles the list and focuses the input; it is not a separate browser Button element. |
ComboBoxPartial | Placement-aware floating list surface with outside-press and Escape dismissal; arbitrary popover children are not composable. |
States
| State | Builder | Description |
|---|---|---|
Open | open_state | The input group owns a live filtered list when the menu is open. |
FocusedPartial | InputState::focus_handle | Input focus drives the field's focused treatment and the default focus menu trigger. |
Hovered triggerPartial | trigger.hover(default.hover()) | The trigger responds to hover, but the local hover fill is broader than v3's trigger text-color change. |
Pressed triggerPartial | trigger_pressed capture token | The local trigger captures the press for dismissal ordering but does not reproduce v3's pressed opacity surface. |
DisabledPartial | is_disabled | Input and trigger are inert and the popover cannot open; browser disabled attributes and root data state are unavailable. |
Read only | is_read_only | The input remains readable while user edits, trigger opening and row selection are blocked, including when a caller keeps the popover open. |
InvalidPartial | is_invalid/error_message | Error content replaces the description and the field is styled invalid; GPUI has no matching attributes. |
RequiredPartial | is_required | The required flag reaches the local field and validator, without browser required/form semantics. |
FilteredPartial | filter/default substring | Rows are limited by the current input and optional custom matcher; the locale-sensitive useFilter default is approximated by case-insensitive substring matching. |
Empty collection | allows_empty_collection | AllowsEmptyCollection keeps the panel open and draws an explicit no-match or custom-value message. |
Disabled optionPartial | disabled_keys | Disabled labels remain visible and cannot be activated, with local muted opacity in place of browser collection semantics. |
Selected optionPartial | selected_keys | Selected labels are tracked and multiple selection can draw an indicator; generic ListBox item data attributes are not emitted. |
Multiple selection | selection_mode(Multiple) | Rows toggle membership without closing and ComboBox.Value can receive the selected labels. |
Custom value | allows_custom_value | Enter commits unmatched input and reports it through the local selection callback. |
Menu trigger | menu_trigger(MenuTrigger) | Focus, input and manual opening modes match the documented trigger choices, including arrow-key opening. |
Focus wrap | should_focus_wrap | Keyboard navigation can wrap between the first and last enabled rows. |
EnteringPartial | placement_entry_offset | The list fades and grows from 95% over 150ms with the list curve and uses the resolved four-pixel placement slide; GPUI still has no transform-origin primitive. |
Exiting | overlay_scope(true) | The retained panel fades and shrinks to 95% over the 100ms Smooth exit; its rows are visual-only and do not block a later pointer target. |
Not ported1 HeroUI entry not ported
| HeroUI v3 | Why it is not ported |
|---|---|
Focus visible trigger | The separate browser trigger focus ring has no GPUI focusable Button equivalent; keyboard focus remains on the input. |