Skip to content

Dropdown

A versatile dropdown/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

rust
use gpui_component::dropdown::{
    Dropdown, DropdownState, DropdownItem, DropdownDelegate,
    DropdownEvent, SearchableVec, DropdownItemGroup
};

Usage

Basic Dropdown

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

Dropdown::new(&dropdown)

With Placeholder

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

Dropdown::new(&dropdown)
    .placeholder("Select a language...")

Searchable Dropdown

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

let dropdown = cx.new(|cx| {
    DropdownState::new(fruits, None, window, cx)
});

Dropdown::new(&dropdown)
    .icon(IconName::Search) // Shows search icon

Custom Item Implementation

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

impl DropdownItem for Country {
    type Value = SharedString;

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

    fn display_title(&self) -> Option<gpui::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())
    }
}

Grouped Items

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

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

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

Dropdown::new(&dropdown)
rust
Dropdown::new(&dropdown).large()
Dropdown::new(&dropdown) // medium (default)
Dropdown::new(&dropdown).small()

Disabled State

rust
Dropdown::new(&dropdown).disabled(true)

Cleanable Dropdown

rust
Dropdown::new(&dropdown)
    .cleanable() // Shows clear button when item is selected

Custom Width and Appearance

rust
Dropdown::new(&dropdown)
    .w(px(320.))                    // Set dropdown width
    .menu_width(px(400.))           // Set menu popup width
    .appearance(false)              // Remove default styling
    .title_prefix("Country: ")      // Add prefix to selected title

Empty State

rust
let dropdown = cx.new(|cx| {
    DropdownState::new(Vec::<SharedString>::new(), None, window, cx)
});

Dropdown::new(&dropdown)
    .empty(
        h_flex()
            .h_24()
            .justify_center()
            .text_color(cx.theme().muted_foreground)
            .child("No options available")
    )

Handle Selection Events

rust
cx.subscribe_in(&dropdown, window, |view, state, event, window, cx| {
    match event {
        DropdownEvent::Confirm(value) => {
            if let Some(selected_value) = value {
                println!("Selected: {:?}", selected_value);
            } else {
                println!("Selection cleared");
            }
        }
    }
});

Programmatic Selection

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

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

// Get current selection
let current_value = dropdown.read(cx).selected_value();

Dynamic Items Update

rust
dropdown.update(cx, |state, cx| {
    let new_items = vec!["New Option 1".into(), "New Option 2".into()];
    state.set_items(new_items, window, cx);
});

API Reference

MethodDescription
new(items, selected_index, window, cx)Create new dropdown state
set_selected_index(index, window, cx)Set selected item by index
set_selected_value(value, window, cx)Set selected item by value
selected_index(cx)Get currently selected index
selected_value()Get currently selected value
set_items(items, window, cx)Update dropdown items
focus(window, cx)Focus the dropdown
MethodDescription
new(state)Create dropdown with state entity
placeholder(str)Set placeholder text
icon(icon)Set custom icon (replaces arrow)
title_prefix(str)Add prefix to selected title
cleanable()Show clear button when selected
disabled(bool)Set disabled state
appearance(bool)Enable/disable default styling
menu_width(width)Set popup menu width
empty(element)Custom empty state element
large()Large size
small()Small size
MethodDescription
title()Display text for the item
display_title()Custom element for selected item display
value()Value returned when selected
matches(query)Custom search matching logic
MethodDescription
sections_count(cx)Number of sections (for grouping)
section(index)Section header element
items_count(section)Number of items in section
item(index)Get item at index path
position(value)Find index of item with value
searchable()Whether delegate supports search
perform_search(query, window, cx)Perform search operation

SearchableVec

MethodDescription
new(items)Create searchable vector
push(item)Add item to vector
MethodDescription
new(title)Create new group with title
items(vec)Set items for the group
EventDescription
Confirm(Option<Value>)Item selected or cleared

Examples

Language Selector

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

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

Dropdown::new(&dropdown)
    .placeholder("Select language...")
    .title_prefix("Language: ")
    .cleanable()

Country/Region Selector

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

impl DropdownItem for Region {
    type Value = SharedString;

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

    fn display_title(&self) -> Option<gpui::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 dropdown = cx.new(|cx| {
    DropdownState::new(regions, None, window, cx)
});

Dropdown::new(&dropdown)
    .placeholder("Select country...")

Integrated with Input Field

rust
// Combined country code + phone input
h_flex()
    .border_1()
    .border_color(cx.theme().input)
    .rounded_lg()
    .w_full()
    .gap_1()
    .child(
        div().w(px(140.)).child(
            Dropdown::new(&country_dropdown)
                .appearance(false) // No border/background
                .py_2()
                .pl_3()
        )
    )
    .child(Divider::vertical())
    .child(
        div().flex_1().child(
            TextInput::new(&phone_input)
                .appearance(false)
                .placeholder("Phone number")
                .pr_3()
                .py_2()
        )
    )

Multi-level Grouped Dropdown

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

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

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

Dropdown::new(&dropdown)
    .menu_width(px(350.))
    .placeholder("Select country...")

Accessibility

  • Full keyboard navigation (Arrow keys, Enter, Escape)
  • Tab to focus dropdown
  • Arrow keys to navigate options
  • Enter to select current option
  • Escape to close menu
  • Screen reader support for selected values
  • Focus management between dropdown and menu
  • Clear focus indicators
  • Proper ARIA attributes for dropdown state

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