Skip to content

Widget VirtualList

MeowLynxSea edited this page Jan 24, 2026 · 5 revisions

VirtualList

A virtualized list widget based on gpui::list(ListState, ...).

VirtualList is the widget-layer companion to the VirtualRow component.

  • VirtualList owns the scrolling + virtualization behavior.
  • VirtualRow owns the per-row identity + spacing/divider contract.

If you’re building a list of “rows”, the recommended pattern is:

VirtualList (widget) + VirtualRow (row shell) + ListItem (row content).

When to use

  • Long scrolling lists (settings pages, search results, file trees)
  • Any UI where you want smooth scrolling and you can structure content as rows

Not a good fit:

  • A single giant scroll view that is one huge item (virtualization can’t help if one item is huge)

API

Widget constructors:

  • virtual_list(state, render_row) -> VirtualList: build the widget from list state
  • virtual_list_state(item_count, alignment, overdraw) -> gpui::ListState: build list state

State helpers:

  • VirtualListController::new(state)
    • reset(element_count)
    • splice(old_range, count)
    • scroll_to_reveal_item(ix)
  • VirtualListHandle::new(item_count, alignment, overdraw)
    • state()
    • controller()

State ownership

  • Hold gpui::ListState (or VirtualListHandle) at the view level.
  • Keep render_row cheap: virtualization helps, but visible rows should still be fast.

Dynamic height and updates

If row content can change height after render (expand/collapse, async-loaded content), notify the list so it can re-measure.

Common choices:

  • simplest: controller.reset(new_count)
  • fine-grained updates: controller.splice(ix..ix + 1, 1)

Defaults

  • VirtualList uses ListSizingBehavior::default().
  • Overdraw is whatever you pass into virtual_list_state(...) / VirtualListHandle::new(...).

Example

use gpui::{AnyElement, ListAlignment, px};
use yororen_ui::{
    component::{list_item, virtual_row},
    widget::{virtual_list, virtual_list_state, VirtualListController},
};

struct MyView {
    state: gpui::ListState,
    controller: VirtualListController,
    items: Vec<String>,
}

impl MyView {
    fn new(cx: &mut gpui::App) -> Self {
        let state = virtual_list_state(0, ListAlignment::Top, px(256.));
        let controller = VirtualListController::new(state.clone());
        Self { state, controller, items: Vec::new() }
    }

    fn render(&mut self) -> impl gpui::IntoElement {
        let items = self.items.clone();
        virtual_list(self.state.clone(), move |ix, _window, _cx| -> AnyElement {
            virtual_row(("row", ix))
                .child(list_item().content(items.get(ix).cloned().unwrap_or_default()))
                .into_any_element()
        })
    }
}

## Example: Using `VirtualListHandle`

`VirtualListHandle` is a convenience type that bundles the state + controller.

```rust
use gpui::{AnyElement, ListAlignment, px};
use yororen_ui::{
    component::{list_item, virtual_row},
    widget::{virtual_list, VirtualListHandle},
};

struct MyView {
    list: VirtualListHandle,
    items: Vec<String>,
}

impl MyView {
    fn new() -> Self {
        Self {
            list: VirtualListHandle::new(0, ListAlignment::Top, px(256.)),
            items: Vec::new(),
        }
    }

    fn set_items(&mut self, items: Vec<String>) {
        let old_len = self.items.len();
        let new_len = items.len();
        self.items = items;
        self.list.controller().splice(0..old_len, new_len);
    }

    fn render(&mut self) -> impl gpui::IntoElement {
        let items = self.items.clone();
        virtual_list(self.list.state(), move |ix, _window, _cx| -> AnyElement {
            virtual_row(("row", ix))
                .child(list_item().content(items.get(ix).cloned().unwrap_or_default()))
                .into_any_element()
        })
    }
}

## Notes

- Always render rows using `VirtualRow(key)` to ensure stable identity under virtualization.
- When item count or row height changes, notify via `VirtualListController.reset/splice`.

Clone this wiki locally