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

Collections

List Box

A list of options, nonselecting until a selection mode is set.

Rust
use herogpui::components::list_box::{ListBox, ListBoxItem};

ListBox 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

Labels use 14px text with 20px lines; descriptions and section headers use 12px text with 16px lines. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{ListBox, ListBoxItem, SelectionMode};
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(220.))
        .child(ListBox::new(
            "list-box",
            vec![
                ListBoxItem::new("inbox", "Inbox"),
                ListBoxItem::new("sent", "Sent"),
                ListBoxItem::new("drafts", "Drafts"),
            ],
        )
        .selection_mode(SelectionMode::Single))
        .into_any_element()])

Row Padding

`row_padding_x` / `row_padding_y` resize the option rows; enabled rows use the 98% ease-out-quart press scale inside a stable footprint, and section headings keep their own inset. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{ActiveTheme, ListBox, ListBoxItem};
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(220.))
        .child(
            ListBox::new(
                "list-box-row-padding",
                vec![
                    ListBoxItem::new("a", "Alpha"),
                    ListBoxItem::new("b", "Beta"),
                ],
            )
            .row_padding_x(px(16.))
            .row_padding_y(px(2.))
            .row_hover_bg(cx.colors().accent.soft()),
        )
        .into_any_element()])

With Disabled Items

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

Rust
use herogpui::prelude::{ListBox, ListBoxItem, SelectionMode};
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(280.))
        .child(
            ListBox::new(
                "list-box-disabled",
                vec![
                    ListBoxItem::new("inbox", "Inbox"),
                    ListBoxItem::new("sent", "Sent"),
                    ListBoxItem::new("drafts", "Drafts"),
                ],
            )
            .selection_mode(SelectionMode::None)
            .disabled_keys([SharedString::from("drafts")]),
        )
        .into_any_element()])

With Sections

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, ListBox, ListBoxItem};
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![gpui::div()
        .w(px(256.))
        .child(ListBox::new(
            "list-box-sections",
            vec![
                ListBoxItem::section("Actions"),
                ListBoxItem::new("new-file", "New file")
                    .description("Create a new file")
                    .shortcut("⌘ N"),
                ListBoxItem::new("edit-file", "Edit file")
                    .description("Make changes")
                    .shortcut("⌘ E"),
                ListBoxItem::separator(),
                ListBoxItem::section("Danger zone"),
                ListBoxItem::new("delete-file", "Delete file")
                    .description("Move to trash")
                    .shortcut("⌘ ⇧ D")
                    .danger(),
            ],
        ).on_action(cx.listener(|this, key: &SharedString, _, cx| {
            this.set_demo_text_value("lb-section-action", key.to_string());
            cx.notify();
        })))
        .into_any_element(), para(&format!("Selected item: {}", self.demo_text_value("lb-section-action")), cx)])

Also calls para (included when you copy).

Multi Select

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::{ListBox, ListBoxItem, SelectionMode};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
use std::collections::HashSet;

let selection = self.list_selection.clone();

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![gpui::div()
        .w(px(280.))
        .child(
            ListBox::new(
                "list-box-multi-select",
                vec![
                    ListBoxItem::new("inbox", "Inbox"),
                    ListBoxItem::new("sent", "Sent"),
                    ListBoxItem::new("spam", "Spam"),
                ],
            )
            .selection_mode(SelectionMode::Multiple)
            .selected_keys(selection.iter().cloned())
            .on_selection_change(cx.listener(
                |this, keys: &HashSet<SharedString>, _, cx| {
                    this.list_selection = keys.clone();
                    cx.notify();
                },
            )),
        )
        .into_any_element()])

Controlled

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, ListBox, ListBoxItem, SelectionMode};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, SharedString, px};
use herogpui::gpui;
use std::collections::HashSet;

let selection = self.list_selection.clone();

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![
        gpui::div()
            .w(px(280.))
            .child(
                ListBox::new(
                    "list-box-controlled",
                    vec![
                        ListBoxItem::new("inbox", "Inbox"),
                        ListBoxItem::new("sent", "Sent"),
                        ListBoxItem::new("spam", "Spam"),
                    ],
                )
                .selection_mode(SelectionMode::Multiple)
                .selected_keys(selection.iter().cloned())
                // `onAction` fires on a press, selection or not.
                .on_action(cx.listener(|this, key: &SharedString, _, cx| {
                    this.set_demo_text_value("lb-action", key.to_string());
                    cx.notify();
                }))
                .on_selection_change(cx.listener(
                    |this, keys: &HashSet<SharedString>, _, cx| {
                        this.list_selection = keys.clone();
                        cx.notify();
                    },
                )),
            )
            .into_any_element(),
        para(&format!("{} selected", selection.len()), cx),
    ])

Also calls para (included when you copy).

Disallow Empty Selection

The inherited selection policy keeps the final selected row selected, including when Escape would otherwise clear the collection. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{ListBox, ListBoxItem, SelectionMode};
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(280.))
            .child(
                ListBox::new(
                    "list-box-keep-selection",
                    vec![
                        ListBoxItem::new("inbox", "Inbox"),
                        ListBoxItem::new("sent", "Sent"),
                    ],
                )
                .selection_mode(SelectionMode::Single)
                .default_selected_keys([SharedString::from("inbox")])
                .disallow_empty_selection(true),
            )
            .into_any_element(),
    ])

Escape Key Behavior

Press Escape while this list is focused. The `None` policy preserves the selection and leaves Escape available to an enclosing surface. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{EscapeKeyBehavior, ListBox, ListBoxItem, SelectionMode};
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(280.))
            .child(
                ListBox::new(
                    "list-box-escape-none",
                    vec![
                        ListBoxItem::new("inbox", "Inbox"),
                        ListBoxItem::new("sent", "Sent"),
                    ],
                )
                .selection_mode(SelectionMode::Single)
                .default_selected_keys([SharedString::from("inbox")])
                .escape_key_behavior(EscapeKeyBehavior::None),
            )
            .into_any_element(),
    ])

Virtualization

`row_height` makes the list geometry computable instead of laid out, so a uniform `VirtualList` builds only the rows in view — one thousand users, fifty pixels each. The fixed-row list caps at `max_h`, shrinks below it in a bounded parent, and PageUp/PageDown move by the visible viewport, including after resize, while skipping disabled stops. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{ActiveTheme, ListBox, ListBoxItem, SelectionMode};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![
        gpui::div()
            .w(px(300.))
            .child(
                ListBox::new("list-box-virtual", virtual_users())
                    .selection_mode(SelectionMode::None)
                    .row_height(px(50.))
                    .max_h(px(400.)),
            )
            .into_any_element(),
        gpui::div()
            .w(px(300.))
            .child(
                ListBox::new("list-box-virtual-var", virtual_users_described())
                    .selection_mode(SelectionMode::None)
                    .estimated_row_height(px(44.))
                    .heading_height(px(28.))
                    .max_h(px(400.)),
            )
            .into_any_element(),
        para(
            "`estimated_row_height` is the other half: rows that are \
                                 *not* all one height, measured as they are built. Every \
                                 third row here carries a description, so it is taller.",
            cx,
        ),
    ])

Also calls virtual_user, virtual_users, virtual_users_described, para (included when you copy).

Virtual List

HeroGPUI extension, not a example: `VirtualList` is the standalone variable-height virtual list. Rows are measured as they are built (every fifth row here is taller) and only rows near the viewport exist; its `VirtualListHandle` scrolls to a row from outside. 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, ListBox, ListBoxItem, Variant, VirtualList, VirtualListScroll};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{AnyElement, 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![
                        Button::new("list-box-jump")
                            .label("Row 500 to top")
                            .variant(Variant::Secondary)
                            .on_press(cx.listener(|this, _, _, cx| {
                                this.virtual_list.scroll_to_item(500, VirtualListScroll::Top);
                                cx.notify();
                            }))
                            .into_any_element(),
                        Button::new("list-box-first")
                            .label("Back to first")
                            .variant(Variant::Secondary)
                            .on_press(cx.listener(|this, _, _, cx| {
                                this.virtual_list.scroll_to_item(0, VirtualListScroll::Top);
                                cx.notify();
                            }))
                            .into_any_element(),
                    ])
            .into_any_element(),
        gpui::div()
            .w(px(320.))
            .rounded(px(12.))
            .border_1()
            .border_color(cx.colors().border)
            .child(
                VirtualList::new("list-box-virtual-list-rows", &self.virtual_list, |ix, _, cx| {
                    let (name, email) = virtual_user(ix);
                    let tall = ix.is_multiple_of(5);
                    gpui::div()
                        .px(px(12.))
                        .py(px(if tall { 14. } else { 6. }))
                        .flex()
                        .flex_col()
                        .child(format!("{ix}. {name}"))
                        .when(tall, |row| {
                            row.child(
                                gpui::div()
                                    .text_color(cx.colors().muted)
                                    .child(email),
                            )
                        })
                        .into_any_element()
                })
                .height(px(260.)),
            )
            .into_any_element(),
    ])

Also calls virtual_user, row (included when you copy).

Tree View

HeroGPUI extension, not a example: `TreeView` is a hierarchy of `TreeItem`s with one tab stop. Up and Down move over the visible rows (skipping the disabled Secrets), Right expands a folder and then enters it, Left collapses it and then returns to the parent, and the chevron toggles a folder under the pointer. Selection follows the `ListBox` model. 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, ListBox, ListBoxItem, SelectionMode, TreeItem, TreeView};
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![
        gpui::div()
            .w(px(280.))
            .child(
                TreeView::new(
                    "list-box-tree",
                    vec![
                        TreeItem::new("src", "src").children(vec![
                            TreeItem::new("components", "components").children(vec![
                                TreeItem::new("button", "button.rs"),
                                TreeItem::new("list-box-view", "tree_view.rs"),
                            ]),
                            TreeItem::new("lib", "lib.rs"),
                        ]),
                        TreeItem::new("docs", "docs").children(vec![
                            TreeItem::new("guide", "guide.md"),
                        ]),
                        TreeItem::new("secrets", "secrets").is_disabled(true),
                        TreeItem::new("readme", "README.md"),
                    ],
                )
                .selection_mode(SelectionMode::Single)
                .default_expanded_keys([SharedString::from("src")])
                .on_selection_change({
                    let view = cx.entity().downgrade();
                    move |keys, _, cx| {
                        let mut keys: Vec<String> = keys.iter().map(|k| k.to_string()).collect();
                        keys.sort();
                        view.update(cx, |this, cx| {
                            this.tree_selected = if keys.is_empty() {
                                SharedString::from("nothing")
                            } else {
                                SharedString::from(keys.join(", "))
                            };
                            cx.notify();
                        })
                        .ok();
                    }
                }),
            )
            .into_any_element(),
        para(&format!("Selected: {}", self.tree_selected), cx),
    ])

Also calls para (included when you copy).

Large Tree View

HeroGPUI extension: two thousand open rows under `max_h`. Only the rows in view are built, and the keyboard cursor scrolls into view. With `SelectionMode::Multiple`, a press or Space seats the anchor and Shift+Up/Down or a Shift press selects the visible rows between it and the target. Each row reports its level and its position among its siblings to assistive technology. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{ListBox, ListBoxItem, SelectionMode, TreeItem, TreeView};
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(280.))
            .child(
                TreeView::new(
                    "list-box-tree-large",
                    (0..50)
                        .map(|group| {
                            TreeItem::new(format!("list-box-{group}"), format!("Folder {group}"))
                                .children((0..40).map(|file| {
                                    TreeItem::new(
                                        format!("list-box-{group}-{file}"),
                                        format!("file-{file}.rs"),
                                    )
                                }))
                        })
                        .collect(),
                )
                .selection_mode(SelectionMode::Multiple)
                .default_expanded_keys((0..50).map(|group| SharedString::from(format!("group-{group}"))))
                .max_h(px(260.)),
            )
            .into_any_element(),
    ])

Custom Check Icon

There is no separate indicator part; a row's `variant` carries the indicator style, so the danger row below shows the same tick in its own colour. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{ListBox, ListBoxItem, SelectionMode};
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(280.))
            .child(
                ListBox::new(
                    "list-box-check",
                    vec![
                        ListBoxItem::new("keep", "Keep"),
                        ListBoxItem::new("delete", "Delete").danger(),
                    ],
                )
                .selection_mode(SelectionMode::Multiple)
                .default_selected_keys([SharedString::from("keep")]),
            )
            .into_any_element(),
    ])

Single selection

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::{ListBox, ListBoxItem, SelectionMode};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
use std::collections::HashSet;

let selection = self.list_selection.clone();
let items = vec![
    ListBoxItem::section("Mail"),
    ListBoxItem::new("inbox", "Inbox").description("24 unread"),
    ListBoxItem::new("sent", "Sent"),
    ListBoxItem::new("drafts", "Drafts").is_disabled(true),
    ListBoxItem::separator(),
    ListBoxItem::new("trash", "Move to trash").danger(),
];

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![gpui::div()
        .w(px(280.))
        .child(
            ListBox::new("list-box-single", items.clone())
                .selection_mode(SelectionMode::Single)
                .selected_keys(selection.iter().cloned())
                .on_selection_change(cx.listener(
                    |this, keys: &HashSet<SharedString>, _, cx| {
                        this.list_selection = keys.clone();
                        cx.notify();
                    },
                )),
        )
        .into_any_element()])

Multiple selection

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::{ListBox, ListBoxItem, SelectionMode};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{SharedString, px};
use herogpui::gpui;
use std::collections::HashSet;

let selection = self.list_selection.clone();
let items = vec![
    ListBoxItem::section("Mail"),
    ListBoxItem::new("inbox", "Inbox").description("24 unread"),
    ListBoxItem::new("sent", "Sent"),
    ListBoxItem::new("drafts", "Drafts").is_disabled(true),
    ListBoxItem::separator(),
    ListBoxItem::new("trash", "Move to trash").danger(),
];

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![gpui::div()
        .w(px(280.))
        .child(
            ListBox::new("list-box-multi", items)
                .selection_mode(SelectionMode::Multiple)
                .selected_keys(selection.iter().cloned())
                .on_selection_change(cx.listener(
                    |this, keys: &HashSet<SharedString>, _, cx| {
                        this.list_selection = keys.clone();
                        cx.notify();
                    },
                )),
        )
        .into_any_element()])

Basic Usage

The plainest list: single selection over rows that carry a label and a description, uncontrolled. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{ListBox, ListBoxItem, SelectionMode};
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(280.))
        .child(
            ListBox::new(
                "list-box-basic",
                vec![
                    ListBoxItem::new("bob", "Bob")
                        .description("bob@example.com"),
                    ListBoxItem::new("alice", "Alice")
                        .description("alice@example.com"),
                ],
            )
            .selection_mode(SelectionMode::Single),
        )
        .into_any_element()])

Controlled Selection

The selected keys live in the caller and are fed back through `selected_keys`; the caption reads the same set the list renders. 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, ListBox, ListBoxItem, SelectionMode};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, SharedString, px};
use herogpui::gpui;
use std::collections::HashSet;

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![
        gpui::div()
            .w(px(280.))
            .child(
                ListBox::new(
                    "list-box-controlled-selection",
                    vec![
                        ListBoxItem::new("opt-1", "Option 1"),
                        ListBoxItem::new("opt-2", "Option 2"),
                        ListBoxItem::new("opt-3", "Option 3"),
                    ],
                )
                .selection_mode(SelectionMode::Multiple)
                .selected_keys(self.lb_pair_selection.iter().cloned())
                .on_selection_change(cx.listener(
                    |this, keys: &HashSet<SharedString>, _, cx| {
                        this.lb_pair_selection = keys.clone();
                        cx.notify();
                    },
                )),
            )
            .into_any_element(),
        para(&format!("{} selected", self.lb_pair_selection.len()), cx),
    ])

Also calls para (included when you copy).

Custom Indicator

The pair-page twin of the check-icon example: a row's `variant` restyles its selection indicator, here a danger tick on the delete row. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{ListBox, ListBoxItem, SelectionMode};
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(280.))
        .child(
            ListBox::new(
                "list-box-indicator",
                vec![
                    ListBoxItem::new("archive", "Archive"),
                    ListBoxItem::new("discard", "Discard").danger(),
                ],
            )
            .selection_mode(SelectionMode::Multiple)
            .default_selected_keys([SharedString::from("archive")]),
        )
        .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.

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()
}
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_users
/// A thousand list rows, for the virtualization demos.
fn virtual_users() -> Vec<ListBoxItem> {
    (0..1000)
        .map(|i| {
            let (name, email) = virtual_user(i);
            ListBoxItem::new(format!("user-{i}"), name).description(email)
        })
        .collect()
}
virtual_users_described
/// The same thousand users, but every third row carries a description and a
/// section header lands every hundred -- rows of three different heights, which
/// is what `estimated_row_height` virtualizes.
fn virtual_users_described() -> Vec<ListBoxItem> {
    let mut items = Vec::with_capacity(1010);
    for i in 0..1000 {
        if i % 100 == 0 {
            items.push(ListBoxItem::section(format!("Batch {}", i / 100 + 1)));
        }
        let (name, email) = virtual_user(i);
        let item = ListBoxItem::new(format!("user-{i}"), name);
        items.push(if i % 3 == 0 {
            item.description(email)
        } else {
            item
        });
    }
    items
}
row
fn row(children: Vec<AnyElement>) -> AnyElement {
    gpui::div()
        .flex()
        .flex_wrap()
        .items_center()
        .justify_center()
        .gap(px(12.))
        .children(children)
        .into_any_element()
}

Anatomy

Rust types

ListBoxListBoxItem

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

List Box styling
MethodValuesDescription
variant(ListBoxItemVariant)ListBoxItemVariant::Default | ListBoxItemVariant::DangerDefault visual variant inherited by options unless an item overrides it.
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: 32 entries ported, 2 partial, 8 not ported. Partial rows are marked in the tables; entries not ported are listed under each table with the reason.

Builders

List Box builders
BuilderValuesDefaultDescription
selection_mode(SelectionMode)SelectionMode::None | SelectionMode::Single | SelectionMode::MultipleSelectionMode::NoneChooses inert, single, or multiple selection semantics; the wrapper sets no default, so the one applies.
selected_keys(keys)Selection—Controlled selected keys; the owner must accept reported changes.
default_selected_keys(keys)Selection—Seeds the list's uncontrolled selected-key set once.
on_selection_change(callback)Fn(keys: Selection) -> ()—Reports the complete selected-key set after pointer and keyboard changes.
disabled_keys(keys)iterable of keys—Removes matching options from pointer and keyboard selection stops.
on_action(callback)Fn(key: Key) -> ()—Reports item activation independently from selection where gives action priority.
variant(ListBoxItemVariant)ListBoxItemVariant::Default | ListBoxItemVariant::DangerListBoxItemVariant::DefaultDefault visual variant inherited by options unless an item overrides it.
disallow_empty_selection(bool)true | falsefalseInherited policy blocks the final-key toggle and Escape clearing.
should_focus_wrap(bool)true | falsefalseInherited policy joins the arrow-key ends.
entry focus onlyPartialbool | FocusStrategyfalseEntry focus follows the selected key or first enabled option, but automatic mount focus is not exposed.
escape_key_behavior(EscapeKeyBehavior)EscapeKeyBehavior::ClearSelection | EscapeKeyBehavior::NoneEscapeKeyBehavior::ClearSelectionControls whether unmodified Escape clears a nonempty selection or remains available to an enclosing interaction.
new(id, items)AnyElement—Items, sections, and separators are supplied as the constructor collection.
new(key, label)Key—Stable option identity used for selection and callbacks.
is_disabled(bool)true | falsefalseDisables one option independently from disabledKeys.
item_content(render)AnyElement | render closure—Replacement row content receives selected, focused, focus-visible, pressed, and disabled state.
indicator(render)AnyElement | render closurecheckmarkReplaces the selected indicator and receives isSelected.
section(label) / separator()AnyElement—Section headers and separators are explicit collection records.
row_height(px)number | undefined48Fixed row geometry enables uniform-list virtualization; the virtual list caps at max_h, shrinks below it in a bounded parent, and pages by the visible viewport while skipping disabled stops.
estimated_row_height(px)number | undefined—Estimated variable row geometry enables measured list virtualization.
heading_height(px)number | undefined48Sets virtual section-row height.
gap(px)number | undefined0Overrides the direct-child spacing.
padding(px)number | undefined0Overrides the list inset.
Not ported8 HeroUI entries not ported
List Box builders not ported
HeroUI v3Why it is not ported
ListBox.selectionBehaviorImplements pinned toggle behavior only; replace-on-focus selection is unavailable.
ListBox.renderEmptyStateAn empty collection renders no synthetic option and has no empty-state slot.
ListBox.layout / orientationThis port exposes the vertical stack layout only.
ListBox.aria-label / aria-labelledbyGPUI has no accessibility tree attributes.
ListBox.className / renderBrowser styles and root-element substitution are unavailable.
ListBox.Item.renderRoot substitution and link attributes are unavailable.
ListLayout.estimatedHeadingHeightSection headings are single-line records rather than independently estimated layouts.
ListLayout.loaderHeight / dropIndicatorThicknessThis collection has neither a loader row nor drag-and-drop indicators.

Parts

List Box parts
PartDescription
ListBoxSelection, focus, keyboard, scrolling, and virtualization owner.
ListBoxItemSelectable option with label, description, icon, shortcut, variant, and render state.
ListBoxBuilt-in check or caller-rendered selected marker.
ListBoxItemPartialNon-selectable heading record grouping following options; nested section ownership is flattened.

States

List Box states
StateBuilderDescription
Selectedselected_keysSelection is shown by the trailing indicator without adding a row fill.
Focusedcursor keyed stateRoving keyboard cursor tracks one enabled option.
Focus visiblewith_focus_ringKeyboard focus draws the status ring and reaches item render state.
PressedinteractionInteractive options shrink to 98% and expose pressed render state.
Hoveredrow hover chromeEnabled options use the default hover fill; does not include hover in its documented item render-prop table.
Disabledis_disabledDisabled options dim and leave pointer, arrow, typeahead, and selection stops.