---
url: /api/list-view.md
---
# ListView

Unified virtualized list component. Renders only visible items plus overscan for smooth scrolling. Supports passive (parent-controlled scroll) and navigable (built-in keyboard/mouse) modes.

## Import

```tsx
import { ListView } from "silvery"
```

## Usage

```tsx
// Passive — parent controls scroll position
<ListView
  items={logs}
  height={20}
  renderItem={(item, index) => <Text>{item.message}</Text>}
/>

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

## Props

### Core

| Prop             | Type                                                        | Default    | Description                                   |
| ---------------- | ----------------------------------------------------------- | ---------- | --------------------------------------------- |
| `items`          | `T[]`                                                       | *required* | Array of items to render                      |
| `height`         | `number`                                                    | *required* | Viewport height in rows                       |
| `renderItem`     | `(item: T, index: number, meta: ListItemMeta) => ReactNode` | *required* | Render function for each item                 |
| `estimateHeight` | `number \| (index: number) => number`                       | `1`        | Estimated height per item in rows             |
| `getKey`         | `(item: T, index: number) => string \| number`              | index      | Key extractor                                 |
| `width`          | `number`                                                    | —          | Viewport width (uses parent width if omitted) |

### Scrolling

| Prop                | Type                                  | Default | Description                                    |
| ------------------- | ------------------------------------- | ------- | ---------------------------------------------- |
| `scrollTo`          | `number`                              | —       | Index to scroll to (ignored when navigable)    |
| `overscan`          | `number`                              | `5`     | Extra items rendered beyond viewport           |
| `maxRendered`       | `number`                              | `100`   | Maximum items rendered at once                 |
| `scrollPadding`     | `number`                              | `2`     | Padding from edge before scrolling (in items)  |
| `overflowIndicator` | `boolean`                             | `false` | Show overflow indicators                       |
| `gap`               | `number`                              | `0`     | Gap between items in rows                      |
| `renderSeparator`   | `() => ReactNode`                     | —       | Custom separator between items                 |
| `listFooter`        | `ReactNode`                           | —       | Content after all items in scroll container    |
| `virtualized`       | `(item: T, index: number) => boolean` | —       | Predicate for already-virtualized prefix items |

### Navigable Mode

| Prop                  | Type                      | Default | Description                                   |
| --------------------- | ------------------------- | ------- | --------------------------------------------- |
| `navigable`           | `boolean`                 | —       | Enable built-in keyboard and mouse navigation |
| `cursorIndex`         | `number`                  | —       | Controlled cursor position                    |
| `onCursorIndexChange` | `(index: number) => void` | —       | Called when cursor moves                      |
| `onSelect`            | `(index: number) => void` | —       | Called when Enter is pressed                  |
| `active`              | `boolean`                 | `true`  | Whether keyboard input is captured            |

### Infinite Scroll

| Prop                    | Type         | Default | Description                            |
| ----------------------- | ------------ | ------- | -------------------------------------- |
| `onEndReached`          | `() => void` | —       | Called when near the end of the list   |
| `onEndReachedThreshold` | `number`     | `5`     | Items from end to trigger onEndReached |

### ListItemMeta

```ts
interface ListItemMeta {
  /** Whether this item is at the cursor position (navigable mode only) */
  isCursor: boolean
}
```

### Ref Handle (ListViewHandle)

| Method                | Type                      | Description                    |
| --------------------- | ------------------------- | ------------------------------ |
| `scrollToItem(index)` | `(index: number) => void` | Imperatively scroll to an item |

## Keyboard Shortcuts (navigable mode)

| Key               | Action                    |
| ----------------- | ------------------------- |
| `j` / `Down`      | Move cursor down          |
| `k` / `Up`        | Move cursor up            |
| `G` / `End`       | Jump to last item         |
| `Home`            | Jump to first item        |
| `PgDn` / `Ctrl+D` | Page down (half viewport) |
| `PgUp` / `Ctrl+U` | Page up (half viewport)   |
| `Enter`           | Select item at cursor     |

## Examples

### Variable Height Items

```tsx
<ListView
  items={messages}
  height={20}
  estimateHeight={(index) => messages[index].lines}
  renderItem={(msg) => <Text>{msg.content}</Text>}
  scrollTo={messages.length - 1}
/>
```

### With Custom Separators

```tsx
<ListView
  items={sections}
  height={15}
  navigable
  renderItem={(item, i, meta) => <Text inverse={meta.isCursor}>{item.title}</Text>}
  renderSeparator={() => <Text color="$border">{"─".repeat(40)}</Text>}
/>
```

### Infinite Scroll

```tsx
<ListView
  items={feed}
  height={20}
  navigable
  renderItem={(item) => <Text>{item.title}</Text>}
  onEndReached={loadMore}
  onEndReachedThreshold={10}
/>
```
