Guide / Virtualized lists

Keep the data large. Keep the mounted tree small.

ListView<T> maps to UI Toolkit's native virtualized ListView, mounting widget rows only for the visible and recycle range.

Start with a typed row builder.

Use a fixed item height for uniform rows. Omitting it selects native dynamic-height virtualization.

ProjectsList.cs
new ListView<Project>(
  projects,
  project => new ProjectRow(project),
  itemHeight: 56f);

Key rows that carry state.

Give every row a deterministic, unique key when it owns focus, subscriptions or local state. A key follows a compatible realized row through insertion and reordering.

ProjectsList.cs
new ListView<Project>(
  projects,
  project => new ProjectRow(project),
  itemHeight: 56f,
  itemKey: project => new WidgetKey(project.Id));
Do not mutate a collection in place.

For reactive replacement, pass State<IReadOnlyList<T>> and replace its complete value. A mutable collection cannot notify the list by itself.

Keep selection outside the list.

Selection is controlled and key-based. User interaction commits the state before the callback runs, while programmatic changes update native selection without replaying the callback.

ProjectsList.cs
var selected = new State<IReadOnlyList<WidgetKey>>(
  Array.Empty<WidgetKey>());

new ListView<Project>(
  projects, BuildRow,
  itemKey: project => new WidgetKey(project.Id),
  selectionMode: ListSelectionMode.Multiple,
  selectedKeys: selected);

Retain a controller when scroll position matters.

Keep the controller at the same ownership boundary as the list configuration. It restores the offset after the native list has its geometry.

ProjectsList.cs
private readonly ListViewController _projectsController = new();

_projectsController.JumpTo(320f);
_projectsController.ScrollTo(new WidgetKey(projectId));
Return to the guides