Select
Pick one value from a dropdown list.
use herogpui::components::select::Select;Select 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.
Use the arrow keys and Enter or Space to select a language. Selection closes the list and keeps focus on the trigger. Enabled options use the 98% press scale over 250ms ease-out-quart inside a stable row slot. 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::{PickerItem, Select};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
let selected = self.select_lang.clone();
let is_open = self.select_open;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![Select::new("select-main", language_items())
.label("Language")
.placeholder("Choose one")
.value(selected.clone())
.is_open(is_open)
// `radius` is the detached panel's corner; the trigger
// keeps the field chrome's own.
.radius(px(8.))
.on_open_change(cx.listener(|this, open: &bool, _, cx| {
this.select_open = *open;
cx.notify();
}))
.on_change(cx.listener(|this, key: &Option<SharedString>, _, cx| {
this.select_lang = key.clone();
this.select_open = false;
cx.notify();
}))
.into_any_element()])Also calls languages, language_items (included when you copy).
The list can enter above its trigger; the placement translation follows the physical side resolved by the viewport positioner. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{PickerItem, Placement, Select};
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![Select::new(
"select-placement-top",
language_items(),
)
.label("Language")
.placeholder("Choose one")
.placement(Placement::Top)
.default_open(true)
.into_any_element()])Also calls languages, language_items (included when you copy).
Selected option labels keep the normal foreground and use a check indicator. Long selected values and natural-height option rows wrap inside the available width instead of being clipped. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{PickerItem, Select};
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(
Select::new(
"select-long-values",
vec![
PickerItem::new(
"long",
"A project name that is intentionally long enough to wrap",
),
PickerItem::new("short", "Short option"),
],
)
.label("Project")
.default_value(Some("long".into()))
.default_open(true),
)
.into_any_element(),
])Clear the selection with the close control or Backspace/Delete on the closed trigger. An empty selection retains the control's layout space. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{FieldVariant, PickerItem, Select, SelectClearButton, 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![
Select::new("select-clear-single", language_items())
.label("Language").default_value(Some("rust".into()))
.clear_button(SelectClearButton::new()).into_any_element(),
Select::new("select-clear-multiple", language_items())
.label("Languages").variant(FieldVariant::Secondary)
.selection_mode(SelectionMode::Multiple)
.default_selected_keys(["rust".into(), "go".into()])
.clear_button(SelectClearButton::new()).into_any_element(),
])Also calls languages, language_items (included when you copy).
`height`, `padding_x` and `is_bare` size the trigger; `row_padding_x` / `row_padding_y` size its option rows. This 28px bare trigger uses 12px trigger and row text, 8px row insets and 4px panel padding. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ActiveTheme, PickerItem, Select};
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![Select::new("select-custom-box", language_items())
.label("Compact")
.placeholder("Choose one")
.height(px(28.))
.padding_x(px(4.))
.is_bare(true)
.trigger_text_size(px(12.))
.row_text_size(px(12.))
.panel_padding(px(4.))
.row_height(px(28.))
.row_padding_x(px(8.))
.row_padding_y(px(2.))
.row_hover_bg(cx.colors().accent.soft())
.font_family(MONO_FONT)
.row_font_family(MONO_FONT)
.default_open(true)
.into_any_element()])Also calls languages, language_items, MONO_FONT (included when you copy).
`trigger_hover_bg` names the fill a hovered trigger fades to, in place of `--field-hover`; focused, invalid and disabled triggers keep their own chrome. `row_hover_bg` does the same for the option rows. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{ActiveTheme, PickerItem, Select};
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![Select::new("select-hover-colours", language_items())
.label("Language")
.placeholder("Choose one")
.trigger_hover_bg(cx.colors().accent.soft())
.row_hover_bg(cx.colors().accent.soft())
.into_any_element()])Also calls languages, language_items (included when you copy).
`row_height` makes the list geometry computable, so gpui's `uniform_list` builds only the rows in view. A thousand options, forty pixels each. The list sizes to the available height. Options remain reachable with the keyboard and mouse wheel. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{PickerItem, Select};
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(Select::new("select-virtual", virtual_picker_items())
.label("User")
.placeholder("Choose one")
.row_height(px(40.)))
.into_any_element(),
])Also calls virtual_user, virtual_picker_items (included when you copy).
An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{PickerItem, Select};
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![Select::new("select-desc", language_items())
.label("Language")
.placeholder("Choose one")
.description("Used for spell-checking")
.into_any_element()])Also calls languages, language_items (included when you copy).
An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{PickerItem, Select};
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![Select::new("select-required", language_items())
.label("Language")
.placeholder("Choose one")
.is_required(true)
.into_any_element()])Also calls languages, language_items (included when you copy).
An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{PickerItem, Select};
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![Select::new("select-disabled", language_items())
.label("Language")
.placeholder("Choose one")
.is_disabled(true)
.into_any_element()])Also calls languages, language_items (included when you copy).
An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{PickerItem, Select};
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![Select::new("select-disabled-opts", language_items())
.label("Language")
.placeholder("Choose one")
.disabled_keys([SharedString::from("typescript"), SharedString::from("go")])
.default_open(true)
.into_any_element()])Also calls languages, language_items (included when you copy).
An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{PickerItem, Select};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![Select::new(
"select-sections",
vec![
PickerItem::new("us", "United States"),
PickerItem::new("ca", "Canada"),
PickerItem::new("mx", "Mexico"),
PickerItem::new("fr", "France"),
PickerItem::new("de", "Germany"),
],
)
.label("Country")
.placeholder("Select a country")
.section_before("us", "North America")
.section_before("fr", "Europe")
.default_open(true)
.into_any_element()])An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{FieldVariant, PickerItem, Select, Surface};
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![Surface::new()
.padding(px(24.))
.child(
Select::new("select-surface", language_items())
.label("Language")
.placeholder("Choose one")
.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::{ActiveTheme, PickerItem, Select, SelectionMode};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, SharedString, px};
use herogpui::gpui;
let sel_multi = self.select_multi.clone();
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
Select::new("select-ctl-multi", language_items())
.label("Languages")
.placeholder("Choose any")
.selection_mode(SelectionMode::Multiple)
.selected_keys(sel_multi.iter().cloned())
.on_selection_change_all(cx.listener(
|this, keys: &[SharedString], _, cx| {
this.select_multi = keys.to_vec();
cx.notify();
},
))
.into_any_element(),
para(&format!("{} selected", sel_multi.len()), 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, Button, PickerItem, Select, Size, Variant};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, SharedString, px};
use herogpui::gpui;
let is_open = self.select_open;
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![
Button::new("select-open-btn")
.label(if is_open { "Close" } else { "Open" })
.variant(Variant::Secondary)
.size(Size::Sm)
.on_press(cx.listener(move |this, _, _, cx| {
this.select_open = !this.select_open;
cx.notify();
}))
.into_any_element(),
para(if is_open { "Open" } else { "Closed" }, cx),
])
.into_any_element(),
Select::new("select-open", language_items())
.label("Language")
.placeholder("Choose one")
.is_open(is_open)
.on_open_change(cx.listener(|this, open: &bool, _, cx| {
this.select_open = *open;
cx.notify();
}))
.into_any_element(),
])Also calls para, languages, language_items (included when you copy).
The list is filled from a request and a spinner shows while it is in flight. The spinner is composed beside the label, since the options are the caller's own data. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{PickerItem, Select, 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![
Select::new("select-async", language_items())
.label("Language")
.placeholder("Loading\u{2026}")
.into_any_element(),
Spinner::new("select-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::{PickerItem, Select};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
let selected = self.select_lang.clone();
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![Select::new("select-indicator", language_items())
.label("Language")
.placeholder("Choose one")
.value(selected.clone())
.on_selection_change(cx.listener(
|this, key: &Option<SharedString>, _, cx| {
this.select_lang = key.clone();
cx.notify();
},
))
.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).
`item_leading` draws an element before each option's label โ v3's `ListBox.Item` render function. The closure takes the row's key and whether it is selected, and returns `None` for a row that takes none. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{PickerItem, Select};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![
Select::new("select-leading", swatch_picker_items())
.label("Accent")
.placeholder("Choose one")
.default_open(true)
.item_leading(|key, _| {
Some(
gpui::div()
.size(px(14.))
.rounded(px(4.))
.bg(swatch_colour(key))
.into_any_element(),
)
})
.into_any_element(),
])Also calls swatch_picker_items, swatch_colour (included when you copy).
The trigger is a `min-h` box, so `padding_y` leaves a single-line value at 36px and only grows a value that has wrapped โ v3's `py-2`. The narrow field below forces the wrap. An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{PickerItem, Select};
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(180.)).child(
Select::new("select-wrap", layout_picker_items())
.label("Layout")
.default_value(Some("side".into()))
.padding_y(px(8.))
.full_width(true),
).into_any_element(),
])Also calls layout_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::{PickerItem, Select};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
let is_open = self.select_open;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![
Select::new("select-trigger-indicator", language_items())
.label("Language")
.placeholder("Choose one")
.default_open(true)
.trigger_indicator(|is_open| {
gpui::div()
.text_size(px(16.))
.child(if is_open { "โ" } else { "+" })
.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::{Chip, ChipLabel, ChipVariant, PickerItem, Select, Size};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
let selected = self.select_lang.clone();
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![Select::new("select-value", language_items())
.label("Language")
.placeholder("Choose one")
.value(selected.clone())
.on_selection_change(cx.listener(
|this, key: &Option<SharedString>, _, cx| {
this.select_lang = key.clone();
cx.notify();
},
))
.value_content(move |value| match value.selected_indices.first() {
Some(i) => gpui::div()
.flex()
.items_center()
.gap(px(8.))
.child(
Chip::new()
.size(Size::Sm)
.variant(ChipVariant::Soft)
.child(ChipLabel::new().child(format!("#{}", i + 1))),
)
.child(languages().get(*i).cloned().unwrap_or_default().to_string())
.into_any_element(),
// v3's own example hands `defaultChildren` back for
// the placeholder case rather than rebuilding it.
None => value.default_children,
})
.into_any_element()])Also calls languages, language_items (included when you copy).
An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{PickerItem, Select};
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![Select::new("select-unc", language_items())
.label("Language")
.placeholder("Choose one")
.default_value(Some(SharedString::from("rust")))
.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::{FieldVariant, PickerItem, Select};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
let selected = self.select_lang.clone();
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(FieldVariant::ALL
.iter()
.filter_map(|v| {
let key = format!("sel-variant-{v:?}");
Some({
Select::new(format!("select-{v:?}"), language_items())
.label(v.label())
.placeholder("Choose one")
.value(selected.clone())
.on_selection_change(cx.listener(
|this, key: &Option<SharedString>, _, cx| {
this.select_lang = key.clone();
cx.notify();
},
))
.variant(*v)
})
}))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::{PickerItem, Select};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
let selected = self.select_lang.clone();
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![gpui::div()
.w(px(400.))
.child(
Select::new("select-full", language_items())
.label("Language")
.placeholder("Choose one")
.value(selected.clone())
.on_selection_change(cx.listener(
|this, key: &Option<SharedString>, _, cx| {
this.select_lang = key.clone();
cx.notify();
},
))
.full_width(true),
)
.into_any_element()])Also calls languages, language_items (included when you copy).
An element expression with window: &mut Window and cx: &mut App in scope.
use herogpui::prelude::{PickerItem, Select, 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![Select::new("select-multi", language_items())
.label("Languages")
.placeholder("Pick several")
.selection_mode(SelectionMode::Multiple)
.default_selected_keys([SharedString::from("rust"), SharedString::from("python")])
.default_open(true)
.into_any_element()])Also calls languages, language_items (included when you copy).
The selected key lives in the caller: the trigger reads `value` and the caption prints the same state. 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, PickerItem, Select};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, SharedString, px};
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![
Select::new("select-controlled", language_items())
.label("Language (controlled)")
.placeholder("Choose one")
.value(self.select_lang_controlled.clone())
.on_change(cx.listener(
|this, key: &Option<SharedString>, _, cx| {
this.select_lang_controlled = key.clone();
cx.notify();
},
))
.into_any_element(),
para(
&match self.select_lang_controlled.as_ref() {
Some(key) => format!(
"Selected: {}",
language_items()
.iter()
.find(|item| item.key() == key)
.map(|item| item.label().to_string())
.unwrap_or_default()
),
None => "Selected: none".to_owned(),
},
cx,
),
])Also calls languages, language_items, para (included when you copy).
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!("select-{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()
}/// Named colours, for the `Select::item_leading` demo: a colour-picking select
/// is the case v3 composes a swatch per row for.
fn swatch_picker_items() -> Vec<PickerItem> {
[
("rose", "Rose"),
("amber", "Amber"),
("teal", "Teal"),
("violet", "Violet"),
]
.into_iter()
.map(|(key, label)| PickerItem::new(key, label))
.collect()
}/// The swatch each [`swatch_picker_items`] key paints.
fn swatch_colour(key: &str) -> gpui::Hsla {
match key {
"rose" => herogpui::core::oklch(0.65, 0.2, 15.),
"amber" => herogpui::core::oklch(0.8, 0.16, 75.),
"teal" => herogpui::core::oklch(0.7, 0.12, 190.),
_ => herogpui::core::oklch(0.6, 0.2, 295.),
}
}/// A two-line-capable layout label, for the `Select::padding_y` demo.
fn layout_picker_items() -> Vec<PickerItem> {
[
("side", "Side by side with the preview pane"),
("stack", "Stacked above the preview pane"),
]
.into_iter()
.map(|(key, label)| PickerItem::new(key, label))
.collect()
}Anatomy
Rust types
Select 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 Select 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 | Expands both the root and trigger to the available width. |
variant(FieldVariant) | FieldVariant::Primary | FieldVariant::Secondary | Selects the shadowed or lower-emphasis field chrome. |
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 trigger 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: 41 entries ported, 7 partial, 9 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 |
|---|---|---|---|
on_clear( | Fn( | โ | Reports a nonempty selection clear request after its selection callback, including controlled mode. |
child( | AnyElement | close icon | Custom content replaces the default icon. |
on_click( | Fn( | โ | Receives the GPUI click event after the root clear callback. |
placeholder( | string | 'Select an item' | Temporary text shown while no option is selected. |
selection_mode( | SelectionMode:: | SelectionMode:: | Chooses single or multiple selection. |
is_open( | true | | โ | Controlled popover open state. |
default_open( | true | | โ | Initial uncontrolled popover state. |
on_open_change( | Fn( | โ | Reports popover state changes. |
disabled_keys( | iterable of keys | โ | Keys which cannot be selected or focused. |
is_disabled( | true | | โ | Disables the field and its interactions. |
value( | Key | | โ | Controlled selection; GPUI separates scalar and multiple values into typed builders. |
default_value( | Key | | โ | Initial uncontrolled scalar or multiple selection, seeded once. |
on_change( | Fn( | โ | Selection callback; scalar and multiple signatures use separate builders. |
is_required( | true | | โ | Draws the required marker and makes an empty live FormField block native submission and focus the trigger. |
is_invalid( | true | | โ | Forces the field into its invalid state. |
name( | string | โ | Exports a live single- or multiple-selection FormField with disabled omission and reset behavior. |
full_width( | true | | false | Expands both the root and trigger to the available width. |
focus_ring( | true | | true | GPUI extension: hides only the trigger's visual focus ring while retaining focus and list behavior. |
variant( | FieldVariant:: | FieldVariant:: | Selects the shadowed or lower-emphasis field chrome. |
new(Partial | AnyElement | | โ | Compound parts; GPUI takes a typed option collection and draws the field structure. |
value_content( | AnyElement | | โ | Custom value content receives the live selected-value projection. |
trigger_indicator( | AnyElement | AnyElement:: | Replaces the trigger chevron and receives the live open state; the built-in SVG rotates over 150ms, while arbitrary caller content remains caller-owned. |
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 trigger with an 8px gap and flips when the preferred side cannot fit and the opposite side has more room. |
Not ported9 HeroUI entries not ported
| HeroUI v3 | Why it is not ported |
|---|---|
Select.ClearButton.className | Classes have no GPUI analogue. |
Select.autoComplete | Browser autofill hint; GPUI has no browser input autocomplete channel. |
Select.className | Native trigger_text_size, row_text_size and panel_padding builders target the painted parts. |
Select.render | Root substitution has no GPUI equivalent. |
Select.Trigger.className | Additional trigger classes. |
Select.Value.className | Additional value classes. |
Select.Value.render | Root substitution has no GPUI equivalent. |
Select.Indicator.className | Additional trigger-indicator classes. |
Select.Popover.className | Additional popover classes. |
Parts
| Part | Description |
|---|---|
SelectClearButton | Optional control composed with clear_button. Focus belongs to the trigger; Backspace/Delete clear a nonempty selection while closed. |
Select | Selection, form and overlay state owner. |
SelectPartial | Focus and press target with optional clear parts; the trigger itself is not separately composable. |
Select | Placeholder or selected-value content with a live render projection. |
Select | Trigger chevron with live open state; the default SVG rotates over 150ms and custom content composes through trigger_indicator. |
SelectPartial | List surface anchored to the trigger; it can flip within the window and supports the full 22-value placement vocabulary. |
States
| State | Builder | Description |
|---|---|---|
Hovered trigger | hover_fade_with_duration_and_easing_suppressed(150ms, EaseSmooth) | Enabled, unfocused trigger hover interpolates the field/default surface over 150ms ease-smooth, suppressing the parent endpoint while a clear button is hovered. |
Focus visible | ring_if_focused | Keyboard focus rings the trigger and applies the field-focus background. Enter and Space finish a single selection without reopening a popup closed by its owner. |
Disabled | is_disabled | Field is dimmed, inert, untabbable and omitted from FormData while disabled. |
Invalid | is_invalid | Invalid field chrome applies the focus background and suppresses the description row. |
Placeholder | SelectionValue::is_placeholder | Empty value uses muted placeholder colour. |
Open indicator | rotating_indicator_with_duration | The built-in down-chevron rotates 180 degrees over the 150ms transition; custom content receives the live open boolean. |
EnteringPartial | overlay_phase Entering | 150ms Smooth fade and 95% zoom match with the resolved four-pixel placement translation; transform origin remains a documented GPUI limitation. |
Exiting | overlay_phase Exiting | 100ms Smooth fade and zoom to 95%. |
Selected option | selected option foreground | Selection keeps the option label at the normal foreground and adds the default check indicator, matching v3's indicator-only state. |
Focused option | with_focus_ring(selected row) | Keyboard focus uses the shared status ring overlay without changing the row's content geometry. |
Disabled option | disabled_keys | Disabled rows are dimmed and skipped by navigation and selection. |
Pressed option | pressed_with_background_ramp(LIST_ITEM_PRESS) | Enabled options shrink to 98% over 250ms ease-out-quart inside a stable slot; disabled rows remain inert. |
Multiple selection | SelectionMode::Multiple | The popover stays open while multiple options are toggled. |