Skip to content
PorabuildHeroGPUI component library
v0.14.0 ยท Apache-2.0

Pickers

Select

Pick one value from a dropdown list.

Rust
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.

Usage

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>.

Rust
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).

Placement

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.

Rust
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).

Long Values & Option Focus

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.

Rust
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(),
    ])

With Clear Button

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.

Rust
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).

Box Customisation

`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.

Rust
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).

Hover Colours

`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.

Rust
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).

Virtualization

`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.

Rust
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).

With Description

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
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).

Required

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
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).

Disabled

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
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).

With Disabled Options

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
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).

With Sections

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
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()])

In Surface

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
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).

Controlled Multiple

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Controlled Open State

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Asynchronous Loading

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.

Rust
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).

Custom Indicator

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Row Leading Content

`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.

Rust
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).

Wrapping Trigger

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.

Rust
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).

Custom Trigger Indicator

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Custom Value

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Uncontrolled

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
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).

Variants

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Full Width

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Multiple Select

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
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).

Controlled

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>.

Rust
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.

languages
fn languages() -> Vec<SharedString> {
    vec![
        "Rust".into(),
        "TypeScript".into(),
        "Python".into(),
        "Go".into(),
        "Swift".into(),
        "Kotlin".into(),
    ]
}
language_items
/// `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()
}
MONO_FONT
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"
};
virtual_user
/// 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()),
    )
}
virtual_picker_items
/// 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()
}
para
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()
}
swatch_picker_items
/// 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()
}
swatch_colour
/// 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.),
    }
}
layout_picker_items
/// 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

SelectSelectClearButton

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

Select styling
MethodValuesDescription
full_width(bool)true | falseExpands both the root and trigger to the available width.
variant(FieldVariant)FieldVariant::Primary | FieldVariant::SecondarySelects the shadowed or lower-emphasis field chrome.
placement(Placement)PlacementThe 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.
Rust
// 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

Select builders
BuilderValuesDefaultDescription
on_clear(callback)Fn() -> ()โ€”Reports a nonempty selection clear request after its selection callback, including controlled mode.
child(content)AnyElementclose iconCustom content replaces the default icon.
on_click(callback)Fn(event: MouseEvent) -> ()โ€”Receives the GPUI click event after the root clear callback.
placeholder(text)string'Select an item'Temporary text shown while no option is selected.
selection_mode(SelectionMode)SelectionMode::Single | SelectionMode::MultipleSelectionMode::SingleChooses single or multiple selection.
is_open(bool)true | falseโ€”Controlled popover open state.
default_open(bool)true | falseโ€”Initial uncontrolled popover state.
on_open_change(callback)Fn(isOpen: bool) -> ()โ€”Reports popover state changes.
disabled_keys(keys)iterable of keysโ€”Keys which cannot be selected or focused.
is_disabled(bool)true | falseโ€”Disables the field and its interactions.
value(Option<SharedString>) / selected_keys(keys)Key | Key[] | nullโ€”Controlled selection; GPUI separates scalar and multiple values into typed builders.
default_value(Option<SharedString>) / default_selected_keys(keys)Key | Key[] | nullโ€”Initial uncontrolled scalar or multiple selection, seeded once.
on_change(callback) / on_selection_change_all(callback)Fn(value: Key | Key[] | null) -> ()โ€”Selection callback; scalar and multiple signatures use separate builders.
is_required(bool)true | falseโ€”Draws the required marker and makes an empty live FormField block native submission and focus the trigger.
is_invalid(bool)true | falseโ€”Forces the field into its invalid state.
name(name)stringโ€”Exports a live single- or multiple-selection FormField with disabled omission and reset behavior.
full_width(bool)true | falsefalseExpands both the root and trigger to the available width.
focus_ring(bool)true | falsetrueGPUI extension: hides only the trigger's visual focus ring while retaining focus and list behavior.
variant(FieldVariant)FieldVariant::Primary | FieldVariant::SecondaryFieldVariant::PrimarySelects the shadowed or lower-emphasis field chrome.
new(id, options)PartialAnyElement | render closureโ€”Compound parts; GPUI takes a typed option collection and draws the field structure.
value_content(render)AnyElement | render closureโ€”Custom value content receives the live selected-value projection.
trigger_indicator(render)AnyElementAnyElement::DefaultChevronReplaces 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)PlacementPlacement::BottomThe 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
Select builders not ported
HeroUI v3Why it is not ported
Select.ClearButton.classNameClasses have no GPUI analogue.
Select.autoCompleteBrowser autofill hint; GPUI has no browser input autocomplete channel.
Select.classNameNative trigger_text_size, row_text_size and panel_padding builders target the painted parts.
Select.renderRoot substitution has no GPUI equivalent.
Select.Trigger.classNameAdditional trigger classes.
Select.Value.classNameAdditional value classes.
Select.Value.renderRoot substitution has no GPUI equivalent.
Select.Indicator.classNameAdditional trigger-indicator classes.
Select.Popover.classNameAdditional popover classes.

Parts

Select parts
PartDescription
SelectClearButtonOptional control composed with clear_button. Focus belongs to the trigger; Backspace/Delete clear a nonempty selection while closed.
SelectSelection, form and overlay state owner.
SelectPartialFocus and press target with optional clear parts; the trigger itself is not separately composable.
SelectPlaceholder or selected-value content with a live render projection.
SelectTrigger chevron with live open state; the default SVG rotates over 150ms and custom content composes through trigger_indicator.
SelectPartialList surface anchored to the trigger; it can flip within the window and supports the full 22-value placement vocabulary.

States

Select states
StateBuilderDescription
Hovered triggerhover_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 visiblering_if_focusedKeyboard 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.
Disabledis_disabledField is dimmed, inert, untabbable and omitted from FormData while disabled.
Invalidis_invalidInvalid field chrome applies the focus background and suppresses the description row.
PlaceholderSelectionValue::is_placeholderEmpty value uses muted placeholder colour.
Open indicatorrotating_indicator_with_durationThe built-in down-chevron rotates 180 degrees over the 150ms transition; custom content receives the live open boolean.
EnteringPartialoverlay_phase Entering150ms Smooth fade and 95% zoom match with the resolved four-pixel placement translation; transform origin remains a documented GPUI limitation.
Exitingoverlay_phase Exiting100ms Smooth fade and zoom to 95%.
Selected optionselected option foregroundSelection keeps the option label at the normal foreground and adds the default check indicator, matching v3's indicator-only state.
Focused optionwith_focus_ring(selected row)Keyboard focus uses the shared status ring overlay without changing the row's content geometry.
Disabled optiondisabled_keysDisabled rows are dimmed and skipped by navigation and selection.
Pressed optionpressed_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 selectionSelectionMode::MultipleThe popover stays open while multiple options are toggled.