List
A powerful List component that provides a virtualized, searchable list interface with support for sections, headers, footers, selection states, and infinite scrolling. The component is built on a delegate pattern that allows for flexible data management and custom item rendering.
Import
rust
use gpui_component::list::{List, ListDelegate, ListItem, ListEvent, ListSeparatorItem};
use gpui_component::IndexPath;
Usage
Basic List
To create a list, you need to implement the ListDelegate
trait for your data:
rust
struct MyListDelegate {
items: Vec<String>,
selected_index: Option<IndexPath>,
}
impl ListDelegate for MyListDelegate {
type Item = ListItem;
fn items_count(&self, _section: usize, _cx: &App) -> usize {
self.items.len()
}
fn render_item(
&self,
ix: IndexPath,
_window: &mut Window,
_cx: &mut Context<List<Self>>,
) -> Option<Self::Item> {
self.items.get(ix.row).map(|item| {
ListItem::new(ix)
.child(Label::new(item.clone()))
.selected(Some(ix) == self.selected_index)
})
}
fn set_selected_index(
&mut self,
ix: Option<IndexPath>,
_window: &mut Window,
cx: &mut Context<List<Self>>,
) {
self.selected_index = ix;
cx.notify();
}
}
// Create the list
let delegate = MyListDelegate {
items: vec!["Item 1".into(), "Item 2".into(), "Item 3".into()],
selected_index: None,
};
let list = cx.new(|cx| List::new(delegate, window, cx));
List with Sections
rust
impl ListDelegate for MyListDelegate {
type Item = ListItem;
fn sections_count(&self, _cx: &App) -> usize {
3 // Number of sections
}
fn items_count(&self, section: usize, _cx: &App) -> usize {
match section {
0 => 5,
1 => 3,
2 => 7,
_ => 0,
}
}
fn render_section_header(
&self,
section: usize,
_window: &mut Window,
cx: &mut Context<List<Self>>,
) -> Option<impl IntoElement> {
let title = match section {
0 => "Section 1",
1 => "Section 2",
2 => "Section 3",
_ => return None,
};
Some(
h_flex()
.px_2()
.py_1()
.gap_2()
.text_sm()
.text_color(cx.theme().muted_foreground)
.child(Icon::new(IconName::Folder))
.child(title)
)
}
fn render_section_footer(
&self,
section: usize,
_window: &mut Window,
cx: &mut Context<List<Self>>,
) -> Option<impl IntoElement> {
Some(
div()
.px_2()
.py_1()
.text_xs()
.text_color(cx.theme().muted_foreground)
.child(format!("End of section {}", section + 1))
)
}
}
List Items with Icons and Actions
rust
fn render_item(
&self,
ix: IndexPath,
_window: &mut Window,
cx: &mut Context<List<Self>>,
) -> Option<Self::Item> {
self.items.get(ix.row).map(|item| {
ListItem::new(ix)
.child(
h_flex()
.items_center()
.gap_2()
.child(Icon::new(IconName::File))
.child(Label::new(item.title.clone()))
)
.suffix(|_, _| {
Button::new("action")
.ghost()
.small()
.icon(IconName::MoreHorizontal)
})
.selected(Some(ix) == self.selected_index)
.on_click(cx.listener(move |this, _, window, cx| {
this.delegate_mut().select_item(ix, window, cx);
}))
})
}
List with Search
The list automatically includes a search input by default. Implement perform_search
to handle queries:
rust
impl ListDelegate for MyListDelegate {
fn perform_search(
&mut self,
query: &str,
_window: &mut Window,
_cx: &mut Context<List<Self>>,
) -> Task<()> {
// Filter items based on query
self.filtered_items = self.all_items
.iter()
.filter(|item| item.to_lowercase().contains(&query.to_lowercase()))
.cloned()
.collect();
Task::ready(())
}
}
// Create list without search input
let list = cx.new(|cx| List::new(delegate, window, cx).no_query());
List with Loading State
rust
impl ListDelegate for MyListDelegate {
fn loading(&self, _cx: &App) -> bool {
self.is_loading
}
fn render_loading(
&self,
_window: &mut Window,
_cx: &mut Context<List<Self>>,
) -> impl IntoElement {
// Custom loading view
v_flex()
.justify_center()
.items_center()
.py_4()
.child(Skeleton::new().h_4().w_full())
.child(Skeleton::new().h_4().w_3_4())
}
}
Infinite Scrolling
rust
impl ListDelegate for MyListDelegate {
fn is_eof(&self, _cx: &App) -> bool {
!self.has_more_data
}
fn load_more_threshold(&self) -> usize {
20 // Trigger when 20 items from bottom
}
fn load_more(&mut self, window: &mut Window, cx: &mut Context<List<Self>>) {
if self.is_loading {
return;
}
self.is_loading = true;
cx.spawn_in(window, async move |view, window| {
// Simulate API call
Timer::after(Duration::from_secs(1)).await;
view.update_in(window, |view, _, cx| {
// Add more items
view.delegate_mut().load_more_items();
view.delegate_mut().is_loading = false;
cx.notify();
});
}).detach();
}
}
List Events
rust
// Subscribe to list events
let _subscription = cx.subscribe(&list, |_, _, event: &ListEvent, _| {
match event {
ListEvent::Select(ix) => {
println!("Item selected at: {:?}", ix);
}
ListEvent::Confirm(ix) => {
println!("Item confirmed at: {:?}", ix);
}
ListEvent::Cancel => {
println!("Selection cancelled");
}
}
});
Different Item Styles
rust
// Basic item with hover effect
ListItem::new(ix)
.child(Label::new("Basic Item"))
.selected(is_selected)
// Item with check icon
ListItem::new(ix)
.child(Label::new("Checkable Item"))
.check_icon(IconName::Check)
.confirmed(is_confirmed)
// Disabled item
ListItem::new(ix)
.child(Label::new("Disabled Item"))
.disabled(true)
// Separator item
ListSeparatorItem::new()
.child(
div()
.h_px()
.w_full()
.bg(cx.theme().border)
)
Custom Empty State
rust
impl ListDelegate for MyListDelegate {
fn render_empty(&self, _window: &mut Window, cx: &mut Context<List<Self>>) -> impl IntoElement {
v_flex()
.size_full()
.justify_center()
.items_center()
.gap_2()
.child(Icon::new(IconName::Search).size_16().text_color(cx.theme().muted_foreground))
.child(
Label::new("No items found")
.text_color(cx.theme().muted_foreground)
)
.child(
Label::new("Try adjusting your search terms")
.text_sm()
.text_color(cx.theme().muted_foreground.opacity(0.7))
)
}
}
Configuration Options
List Configuration
rust
List::new(delegate, window, cx)
.max_h(px(400.)) // Set maximum height
.scrollbar_visible(false) // Hide scrollbar
.selectable(false) // Disable selection
.no_query() // Remove search input
.paddings(Edges::all(px(8.))) // Set internal padding
Scrolling Control
rust
// Scroll to specific item
list.update(cx, |list, cx| {
list.scroll_to_item(
IndexPath::new(0).section(1), // Row 0 of section 1
ScrollStrategy::Center,
window,
cx,
);
});
// Scroll to selected item
list.update(cx, |list, cx| {
list.scroll_to_selected_item(window, cx);
});
// Set selected index without scrolling
list.update(cx, |list, cx| {
list.set_selected_index(Some(IndexPath::new(5)), window, cx);
});
API Reference
List
Method | Description |
---|---|
new(delegate, window, cx) | Create a new list with delegate |
max_h(height) | Set maximum height |
scrollbar_visible(bool) | Show/hide scrollbar |
no_query() | Remove search input |
selectable(bool) | Enable/disable selection |
paddings(edges) | Set internal padding |
set_selected_index(ix, window, cx) | Set selected item |
scroll_to_item(ix, strategy, window, cx) | Scroll to specific item |
scroll_to_selected_item(window, cx) | Scroll to selected item |
ListDelegate
Method | Description |
---|---|
sections_count(cx) | Number of sections (default: 1) |
items_count(section, cx) | Number of items in section |
render_item(ix, window, cx) | Render list item |
render_section_header(section, window, cx) | Render section header |
render_section_footer(section, window, cx) | Render section footer |
render_empty(window, cx) | Render empty state |
render_initial(window, cx) | Render initial state |
render_loading(window, cx) | Render loading state |
perform_search(query, window, cx) | Handle search query |
set_selected_index(ix, window, cx) | Update selection |
confirm(secondary, window, cx) | Handle item confirmation |
cancel(window, cx) | Handle selection cancel |
loading(cx) | Return loading state |
is_eof(cx) | Return if more data available |
load_more_threshold() | Threshold for triggering load more |
load_more(window, cx) | Load more data |
ListItem
Method | Description |
---|---|
new(id) | Create new list item |
selected(bool) | Set selected state |
confirmed(bool) | Set confirmed state (shows check) |
disabled(bool) | Set disabled state |
check_icon(icon) | Set check icon |
suffix(fn) | Add suffix element |
on_click(fn) | Click handler |
on_mouse_enter(fn) | Mouse enter handler |
ListEvent
Variant | Description |
---|---|
Select(IndexPath) | Item was selected |
Confirm(IndexPath) | Item was confirmed (clicked/Enter) |
Cancel | Selection was cancelled (Escape) |
IndexPath
Method | Description |
---|---|
new(row) | Create new index path |
section(section) | Set section index |
row | Row index |
section | Section index |
Examples
File Browser List
rust
struct FileBrowserDelegate {
files: Vec<FileInfo>,
selected: Option<IndexPath>,
}
#[derive(Clone)]
struct FileInfo {
name: String,
is_directory: bool,
size: Option<u64>,
}
impl ListDelegate for FileBrowserDelegate {
type Item = ListItem;
fn render_item(&self, ix: IndexPath, window: &mut Window, cx: &mut Context<List<Self>>) -> Option<Self::Item> {
self.files.get(ix.row).map(|file| {
let icon = if file.is_directory {
IconName::Folder
} else {
IconName::File
};
ListItem::new(ix)
.child(
h_flex()
.items_center()
.justify_between()
.w_full()
.child(
h_flex()
.items_center()
.gap_2()
.child(Icon::new(icon))
.child(Label::new(file.name.clone()))
)
.when_some(file.size, |this, size| {
this.child(
Label::new(format_size(size))
.text_sm()
.text_color(cx.theme().muted_foreground)
)
})
)
.selected(Some(ix) == self.selected)
})
}
}
Contact List with Sections
rust
struct ContactListDelegate {
contacts_by_letter: BTreeMap<char, Vec<Contact>>,
selected: Option<IndexPath>,
}
impl ListDelegate for ContactListDelegate {
type Item = ListItem;
fn sections_count(&self, _cx: &App) -> usize {
self.contacts_by_letter.len()
}
fn render_section_header(&self, section: usize, _window: &mut Window, cx: &mut Context<List<Self>>) -> Option<impl IntoElement> {
let letter = self.contacts_by_letter.keys().nth(section)?;
Some(
div()
.px_3()
.py_2()
.bg(cx.theme().background)
.border_b_1()
.border_color(cx.theme().border)
.child(
Label::new(letter.to_string())
.text_lg()
.text_color(cx.theme().accent_foreground)
.font_weight(FontWeight::BOLD)
)
)
}
}
Accessibility
- Keyboard Navigation: Use arrow keys to navigate items, Enter to confirm, Escape to cancel
- Focus Management: Proper focus indication and management
- Screen Reader Support: Semantic markup and ARIA attributes
- Search: Built-in search functionality with keyboard shortcuts
- Selection States: Clear visual and programmatic indication of selection
- Loading States: Accessible loading indicators and announcements
Performance
- Virtualization: Only renders visible items for large datasets
- Efficient Updates: Optimized re-rendering with proper change detection
- Memory Management: Automatic cleanup of off-screen items
- Smooth Scrolling: Hardware-accelerated scrolling with momentum
- Lazy Loading: Built-in support for infinite scrolling and pagination