Skip to content

VirtualList

Efficiently renders large lists by windowing only the visible range.

Usage

ui.virtualList({
  id: "items",
  items: state.items,
  itemHeight: 1,
  renderItem: (item, index, focused) =>
    ui.text(focused ? `> ${item}` : `  ${item}`, {
      key: String(index),
      style: focused ? { bold: true } : {},
    }),
  onSelect: (item) => openItem(item),
})

Variable-Height (Estimated + Corrected)

ui.virtualList({
  id: "chat",
  items: state.messages,
  estimateItemHeight: (msg) => (msg.preview ? 3 : 2),
  renderItem: (msg, _index, focused) =>
    ui.column(
      { gap: 0 },
      [
        ui.text(focused ? `> ${msg.author}` : `  ${msg.author}`),
        ui.text(msg.text),
        msg.preview ? ui.text(msg.preview, { style: { dim: true } }) : null,
      ].filter(Boolean),
    ),
})

Props

Prop Type Default Description
id string required Widget identifier for focus and routing
items T[] required Items to render
itemHeight number \| (item, index) => number 1 Exact item height (fixed or precomputed variable)
estimateItemHeight number \| (item, index) => number - Estimated height for variable-height virtualization
measureItemHeight (item, index, ctx) => number internal measurer Optional custom measurement callback for estimate mode
renderItem (item, index, focused) => VNode required Render function for each item
overscan number 3 Extra items rendered above/below viewport
keyboardNavigation boolean true Enable arrow/page/home/end navigation
wrapAround boolean false Wrap selection from end to start
scrollDirection "traditional" \| "natural" "traditional" Mouse-wheel direction mode for this list
selectionStyle TextStyle - Optional style override for the selected row highlight
onScroll (scrollTop, range) => void - Scroll callback with visible range
onSelect (item, index) => void - Selection callback
focusConfig FocusConfig - Control focus visuals; { indicator: "none" } suppresses focused item highlight

Behavior

  • Arrow Up/Down navigates items. Page Up/Down and Home/End jump by page or to boundaries.
  • Mouse scroll wheel scrolls the list (3 lines per tick).
  • scrollDirection: "traditional" (default): wheel down moves viewport down.
  • scrollDirection: "natural": wheel down moves content down (trackpad-style).
  • The onScroll callback fires for both keyboard navigation and mouse wheel input.

Notes

  • Use itemHeight for fixed heights or when exact heights are known up front.
  • Use estimateItemHeight when true height depends on rendered content/width.
  • measureItemHeight is only used in estimate mode. It receives { width, estimatedHeight, vnode } for each rendered item and can override the internal simple measurer.
  • Returned measureItemHeight values are normalized before entering the measured-height cache: non-finite or non-positive values fall back to estimatedHeight, and positive values are truncated to whole cells with a minimum of 1.
  • In estimate mode, visible items are measured and cached, then scroll math is corrected on the following frame.
  • Measured-height cache is reused only while viewport width and item count stay the same; changing either triggers remeasurement.
  • renderItem receives a focused flag for styling.
  • selectionStyle still applies to the selected row in estimate mode.
  • The range passed to onScroll is [startIndex, endIndex) and includes overscan.