Virtual list
A scrolling list for thousands or millions of items. Only the rows in view are built: render(item) is called when an item scrolls into view, so the list costs the same whatever its length. Every row has the same item_height. pz.virtual_column is the building block underneath (rows by index, inside any scrollable), and Table uses it for its rows too.
ui.virtual_list · Category: Lists and menus · Full reference →


files = [f"src/module_{i:06}.py" for i in range(1_000_000)]pz.container(ui.virtual_list( files, lambda f: ui.list_item(f, icon="file-code", selected=f.endswith("02.py")), height=240,), width=420)When to use
Section titled “When to use”- For logs, search results, file lists or any list that can grow past a few hundred rows.
- For short lists, a
pz.columnof List items is simpler.
import pyzed as pzfrom pyzed import ui
class Logs(pz.App): def __init__(self): self.lines = [f"[{i:06}] request handled" for i in range(500_000)]
def view(self): return ui.virtual_list(self.lines, lambda line: pz.text(line, font="mono", size=12), item_height=20, id="logs")Variants
Section titled “Variants”Options
Section titled “Options”

Code
pz.container(ui.virtual_list([f"row {i:,}" for i in range(1_000_000)], lambda r: ui.list_item(r, icon="file"), height=150), width=260)

item_height=20Code
pz.container(ui.virtual_list([f"[{i:06}] request handled" for i in range(100_000)], lambda line: pz.text(line, font="mono", size=12), item_height=20, height=150), width=260)

spacing=6Code
pz.container(ui.virtual_list(list(range(10_000)), lambda i: ui.card(ui.label(f"Card {i}"), padding=8), item_height=40, spacing=6, height=150), width=260)Examples
Section titled “Examples”Rows by index
Section titled “Rows by index”pz.virtual_column(count, item, item_height=...) calls item(index), handy when the rows come from an array, a database cursor or a computed value. Scroll it like any scrollable: row i starts at i * (item_height + spacing).


pz.container(pz.scrollable( pz.virtual_column(10_000_000, lambda i: pz.text(f"{i:>10,} {i * i:>22,}", font="mono", size=12), item_height=22), height=150,), width=420)Events
Section titled “Events”Virtual list has 1 event of its own, listed below. Like every widget it also has the 18 common events (click, double_click, right_click, mouse_enter, drag, key_down, resize…), attached with .on(...): see Events and Events of every component.
| Event | Handler gets | When |
|---|---|---|
on_scroll |
(info) |
Callable that receives the scroll viewport, as in pz.scrollable. |
Handler gets is what Pyzed passes to your callable: () means nothing. Any handler can also be a message (a value that is not callable), which goes to update().
Handling the events
Section titled “Handling the events”The same events written three ways. Imperative: methods of the app change self. Declarative: the handlers produce messages and one update() decides. Functional: a pure function draws an immutable state and asks for changes with set_state(...). See Imperative, declarative and functional.
class Logs(pz.App): def __init__(self): self.lines = [f"[{i:06}] request handled" for i in range(100_000)] self.top = 0
def scrolled(self, info): # on_scroll: offsets and sizes of the viewport self.top = int(info["y"] // 20)
def view(self): return pz.column([ ui.label(f"Line {self.top + 1} of {len(self.lines)}"), ui.virtual_list(self.lines, lambda line: pz.text(line, font="mono", size=12), item_height=20, height=200, on_scroll=self.scrolled), ], spacing=6)from dataclasses import dataclass
@dataclassclass Scrolled: info: dict
class Logs(pz.App): lines = [f"[{i:06}] request handled" for i in range(100_000)] top = 0
def update(self, message): match message: case Scrolled(info): self.top = int(info["y"] // 20)
def view(self): return ui.virtual_list(self.lines, lambda line: pz.text(line, size=12), item_height=20, height=200, on_scroll=Scrolled)from dataclasses import dataclass
LINES = [f"[{i:06}] request handled" for i in range(100_000)]
@dataclass(frozen=True)class State: top: int = 0
def view(state, set_state): return pz.column([ ui.label(f"Line {state.top + 1}"), ui.virtual_list(LINES, lambda line: pz.text(line, size=12), item_height=20, height=200, on_scroll=lambda info: set_state(top=int(info["y"] // 20))), ], spacing=6)Properties and API
Section titled “Properties and API”ui.virtual_list
Section titled “ui.virtual_list”virtual_list( items, render, *, item_height=26, spacing=0, width='fill', height='fill', padding=None, overscan=4, id=None, on_scroll=None, theme=None,)A scrolling list that only builds the rows in view, for thousands or millions of items. render(item) returns the widget for one item and is called when it scrolls into view. Every row has the same height.
| Property | Default | Description |
|---|---|---|
items |
required | The items: a list or any sequence (other iterables are turned into a list). |
render |
required | Function that receives an item and returns its widget. |
item_height |
26 |
Height of every row, in pixels. |
spacing |
0 |
Space between children, in pixels. |
width |
"fill" |
Width: pixels, "fill", "shrink" or "fill:N" (proportion). |
height |
"fill" |
Height: pixels, "fill", "shrink" or "fill:N" (proportion). |
padding |
None |
Inner padding: 8, (vertical, horizontal) or (top, right, bottom, left). |
overscan |
4 |
Extra rows built above and below the visible ones. |
id |
None |
Stable identifier used to focus or scroll it with a Command. |
theme |
None |
Theme to use instead of the current one (pyzed.theme.current()). |
Events: on_scroll (events).
pz.virtual_column
Section titled “pz.virtual_column”virtual_column(count, item, *, item_height, spacing=0.0, overscan=4, width=None)A column of count rows of the same height that only builds the rows in view: item(index) is called when row index scrolls into view, and rows that leave it are dropped. A column of a million rows costs the same as one of fifty. Put it in a scrollable (it virtualizes against the visible area of the closest one, or the window).
| Property | Default | Description |
|---|---|---|
count |
required | Number of rows. |
item |
required | Function that receives a row index and returns its widget. |
item_height |
required | Height of every row, in pixels. |
spacing |
0.0 |
Space between rows, in pixels. |
overscan |
4 |
Extra rows built above and below the visible ones, so fast scrolling shows no gaps. |
width |
None |
Width: pixels, "fill", "shrink" or "fill:N" (proportion). |