Select

Framework

A select component that allows users to choose from a list of options.

Supports search functionality, grouped items, custom rendering, and various states. Built with keyboard navigation and accessibility in mind.

#Import

use gpui_kit::component::select::{
    Select, SelectState, SelectItem, SelectDelegate,
    SelectEvent, SearchableVec, SelectGroup
};
SlintNo Slint version of this example yet.

#Usage

#Framework

You can create a basic select dropdown by initializing a SelectState with a list of items.

The first type parameter of SelectState is the items for the state, which must implement the SelectItem trait.

The built-in implementations of SelectItem include common types like String, SharedString, and &'static str.

let state = cx.new(|cx| {
    SelectState::new(
        vec!["Apple", "Orange", "Banana"],
        Some(IndexPath::default()), // Select first item
        window,
        cx,
    )
});

Select::new(&state)
SlintNo Slint version of this example yet.

#Placeholder

let state = cx.new(|cx| {
    SelectState::new(
        vec!["Rust", "Go", "JavaScript"],
        None, // No initial selection
        window,
        cx,
    )
});

Select::new(&state)
    .placeholder("Select a language...")
SlintNo Slint version of this example yet.

#Accessibility

Give the control a name that stays the same when the selection changes:

Select::new(&state)
    .accessibility_label("Programming language")
    .placeholder("Choose a language")
SlintNo Slint version of this example yet.

The accessible value uses the committed item’s title() and any title_prefix. A custom display_title() remains visual presentation. Searching does not change that committed value. With no selection, the accessible value uses the placeholder. Enabled controls expose accessible activation to open or close the popup.

#Searchable

Use searchable(true) to enable search functionality within the dropdown.

let fruits = SearchableVec::new(vec![
    "Apple", "Orange", "Banana", "Grape", "Pineapple",
]);

let state = cx.new(|cx| {
    SelectState::new(fruits, None, window, cx).searchable(true)
});

Select::new(&state)
    .icon(IconName::Search) // Shows search icon
SlintNo Slint version of this example yet.

#Impl SelectItem

By default, we have implmemented SelectItem for common types like String, SharedString and &'static str. You can also create your own item types by implementing the SelectItem trait.

This is useful when you want to display complex data structures, and also want get that data type from select_value method.

You can also customize the search logic by overriding the matches method.

#[derive(Debug, Clone)]
struct Country {
    name: SharedString,
    code: SharedString,
}

impl SelectItem for Country {
    type Value = SharedString;

    fn title(&self) -> SharedString {
        self.name.clone()
    }

    fn display_title(&self) -> Option<gpui_kit::AnyElement> {
        // Custom display for selected item
        Some(format!("{} ({})", self.name, self.code).into_any_element())
    }

    fn value(&self) -> &Self::Value {
        &self.code
    }

    fn matches(&self, query: &str) -> bool {
        // Custom search logic
        self.name.to_lowercase().contains(&query.to_lowercase()) ||
        self.code.to_lowercase().contains(&query.to_lowercase())
    }
}
SlintNo Slint version of this example yet.

#Group Items

let mut grouped_items = SearchableVec::new(vec![]);

// Group countries by first letter
grouped_items.push(
    SelectGroup::new("A")
        .items(vec![
            Country { name: "Australia".into(), code: "AU".into() },
            Country { name: "Austria".into(), code: "AT".into() },
        ])
);
grouped_items.push(
    SelectGroup::new("B")
        .items(vec![
            Country { name: "Brazil".into(), code: "BR".into() },
            Country { name: "Belgium".into(), code: "BE".into() },
        ])
);

let state = cx.new(|cx| {
    SelectState::new(grouped_items, None, window, cx)
});

Select::new(&state)
SlintNo Slint version of this example yet.

#Sizes

Select::new(&state).large()
Select::new(&state) // medium (default)
Select::new(&state).small()
SlintNo Slint version of this example yet.

#Disabled State

Select::new(&state).disabled(true)
SlintNo Slint version of this example yet.

#Cleanable

Select::new(&state)
    .cleanable(true) // Show clear button when item is selected
SlintNo Slint version of this example yet.

#Custom Appearance

Select::new(&state)
    .w(px(320.))                    // Set dropdown width
    .menu_width(px(400.))           // Set menu popup width
    .menu_max_h(rems(10.))          // Set menu max height (default: 20rem)
    .appearance(false)              // Remove default styling
    .title_prefix("Country: ")      // Add prefix to selected title
SlintNo Slint version of this example yet.

#Empty State

let state = cx.new(|cx| {
    SelectState::new(Vec::<SharedString>::new(), None, window, cx)
});

Select::new(&state)
    .empty(
        h_flex()
            .h_24()
            .justify_center()
            .text_color(cx.theme().muted_foreground)
            .child("No options available")
    )
SlintNo Slint version of this example yet.

#Events

cx.subscribe_in(&state, window, |view, state, event, window, cx| {
    match event {
        SelectEvent::Confirm(value) => {
            if let Some(selected_value) = value {
                println!("Selected: {:?}", selected_value);
            } else {
                println!("Selection cleared");
            }
        }
    }
});
SlintNo Slint version of this example yet.

#Mutating

// Set by index
state.update(cx, |state, cx| {
    state.set_selected_index(Some(IndexPath::default().row(2)), window, cx);
});

// Set by value (requires PartialEq on Value type)
state.update(cx, |state, cx| {
    state.set_selected_value(&"US".into(), window, cx);
});

// Get current selection
let current_value = state.read(cx).selected_value();
SlintNo Slint version of this example yet.

Update items:

state.update(cx, |state, cx| {
    let new_items = vec!["New Option 1".into(), "New Option 2".into()];
    state.set_items(new_items, window, cx);
});
SlintNo Slint version of this example yet.

#Language Selector

let languages = SearchableVec::new(vec![
    "Rust".into(),
    "TypeScript".into(),
    "Go".into(),
    "Python".into(),
    "JavaScript".into(),
]);

let state = cx.new(|cx| {
    SelectState::new(languages, None, window, cx)
});

Select::new(&state)
    .placeholder("Select language...")
    .title_prefix("Language: ")
SlintNo Slint version of this example yet.

#Country/Region Selector

#[derive(Debug, Clone)]
struct Region {
    name: SharedString,
    code: SharedString,
    flag: SharedString,
}

impl SelectItem for Region {
    type Value = SharedString;

    fn title(&self) -> SharedString {
        self.name.clone()
    }

    fn display_title(&self) -> Option<gpui_kit::AnyElement> {
        Some(
            h_flex()
                .items_center()
                .gap_2()
                .child(self.flag.clone())
                .child(format!("{} ({})", self.name, self.code))
                .into_any_element()
        )
    }

    fn value(&self) -> &Self::Value {
        &self.code
    }
}

let regions = vec![
    Region {
        name: "United States".into(),
        code: "US".into(),
        flag: "🇺🇸".into()
    },
    Region {
        name: "Canada".into(),
        code: "CA".into(),
        flag: "🇨🇦".into()
    },
];

let state = cx.new(|cx| {
    SelectState::new(regions, None, window, cx)
});

Select::new(&state)
    .placeholder("Select country...")
SlintNo Slint version of this example yet.

#Integrated with Input Field

// Combined country code + phone input
h_flex()
    .border_1()
    .border_color(cx.theme().input)
    .rounded(cx.theme().radius_lg)
    .w_full()
    .gap_1()
    .child(
        div().w(px(140.)).child(
            Select::new(&country_state)
                .appearance(false) // No border/background
                .py_2()
                .pl_3()
        )
    )
    .child(Separator::vertical())
    .child(
        div().flex_1().child(
            Input::new(&phone_input)
                .appearance(false)
                .placeholder("Phone number")
                .pr_3()
                .py_2()
        )
    )
SlintNo Slint version of this example yet.

#Multi-level Grouped Select

let mut grouped_countries = SearchableVec::new(vec![]);

for (continent, countries) in countries_by_continent {
    grouped_countries.push(
        SelectGroup::new(continent)
            .items(countries)
    );
}

let state = cx.new(|cx| {
    SelectState::new(grouped_countries, None, window, cx)
});

Select::new(&state)
    .menu_width(px(350.))
    .placeholder("Select country...")
SlintNo Slint version of this example yet.

#Keyboard Shortcuts

KeyAction
TabFocus dropdown
EnterOpen menu or select current item
Up/DownNavigate options (opens menu if closed)
EscapeClose menu
SpaceOpen menu

#Theming

The dropdown respects the current theme and uses the following theme tokens:

  • background - Dropdown input background
  • input - Border color
  • foreground - Text color
  • muted_foreground - Placeholder and disabled text
  • accent - Selected item background
  • accent_foreground - Placeholder text color
  • border - Menu border
  • radius - Border radius