Skip to content

ListView

Unified virtualized list component. Merges core virtualization (viewport rendering, placeholders) with navigation (keyboard, mouse wheel, cursor state) into a single component. The recommended list component for all use cases.

Import

tsx
import { ListView } from "silvery"

Props

PropTypeDefaultDescription
itemsT[]requiredArray of items to render
heightnumberrequiredHeight of the viewport in rows
renderItem(item: T, index: number, meta: ListItemMeta) => ReactNoderequiredRender function for each item
estimateHeightnumber | ((index: number) => number)1Estimated height per item (fallback before measurement)
scrollTonumber--Index to scroll to (declarative). Ignored when navigable=true
overscannumber5Extra items to render beyond viewport
maxRenderednumber100Maximum items to render at once
scrollPaddingnumber2Padding from edge before scrolling (in items)
overflowIndicatorbooleanfalseShow overflow indicators
getKey(item: T, index: number) => string | numberindexKey extractor
widthnumber--Viewport width (uses parent width if not specified)
gapnumber0Gap between items in rows
renderSeparator() => ReactNode--Render separator between items
onWheel(event: { deltaY: number }) => void--Mouse wheel handler (passive mode only)
onEndReached() => void--Called when visible range nears end (infinite scroll)
onEndReachedThresholdnumber5Items from end to trigger onEndReached
listFooterReactNode--Content rendered after all items
virtualized(item: T, index: number) => boolean--Predicate for items already virtualized
PropTypeDefaultDescription
navigableboolean--Enable built-in keyboard and mouse wheel navigation
cursorIndexnumber--Controlled cursor index
onCursorIndexChange(index: number) => void--Called when cursor position changes
onSelect(index: number) => void--Called when Enter is pressed on cursor item
activebooleantrueWhether this ListView is active for keyboard input

History / Surface

PropTypeDefaultDescription
surfaceIdstring--Surface identity for search/selection routing
textAdapterListTextAdapter<T>--Text extraction for search/history
historyListViewHistoryConfig<T>--History configuration

ListItemMeta

ts
interface ListItemMeta {
  isCursor: boolean
}

Ref: ListViewHandle

ts
interface ListViewHandle {
  scrollToItem(index: number): void
  getHistoryBuffer(): HistoryBuffer | null
  getComposedViewport(): ComposedViewport | null
}

Usage

tsx
// Passive (parent controls scroll)
<ListView
  items={logs}
  height={20}
  renderItem={(item, index) => <LogEntry data={item} />}
  estimateHeight={() => 3}
/>

// Navigable (built-in j/k, arrows, PgUp/PgDn, Home/End, G, mouse wheel)
<ListView
  items={items}
  height={20}
  navigable
  renderItem={(item, i, meta) => (
    <Text>{meta.isCursor ? '> ' : '  '}{item.name}</Text>
  )}
  onSelect={(index) => openItem(items[index])}
/>

Dynamic Height Measurement

ListView automatically measures each rendered item's actual height after layout and uses those measurements for accurate scroll calculations. The estimateHeight prop is used as a fallback for items that haven't been rendered yet (above or below the viewport). As the user scrolls, measurements accumulate and scroll accuracy improves.

This means variable-height items (e.g., cards with 3-6 rows depending on title length and children) work correctly without manual height calculation. Set estimateHeight to the most common item height for a smooth initial render.

See Also

Released under the MIT License.