# Virtualizer

URL: https://ark-ui.com/docs/react/utilities/virtualizer
LLM: https://ark-ui.com/llms.txt/utilities/virtualizer

Render large lists, grids, and window-scrolled content by only mounting the items in view.

---

---
id: virtualizer
title: Virtualizer
description: Render large lists, grids, and window-scrolled content by only mounting the items in view.
status: new
---



## Usage

The virtualizer keeps the DOM small by rendering only the items that intersect the scroll viewport, plus a few extra for
smooth scrolling. It comes in three flavors, each with a hook and a matching set of components:

- `useListVirtualizer` + `ListVirtualizer` for vertical or horizontal lists that scroll inside their own container.
- `useGridVirtualizer` + `GridVirtualizer` for two-dimensional grids of rows and cells.
- `useWindowVirtualizer` + `WindowVirtualizer` for lists that scroll with the window or the nearest scrollable ancestor.

Create a virtualizer with one of the hooks, pass it to `Root` as `value`, render the spacer with `Content`, and map the
virtual items to `Item` elements.

```tsx
import { ListVirtualizer, useListVirtualizer } from '@ark-ui/react/virtualizer'

const virtualizer = useListVirtualizer({
  count: items.length,
  estimatedSize: () => 48,
})

<ListVirtualizer.Root value={virtualizer}>
  <ListVirtualizer.Content>
    {virtualizer.getVirtualItems().map((item) => (
      <ListVirtualizer.Item key={item.key} item={item}>
        {items[item.index]}
      </ListVirtualizer.Item>
    ))}
  </ListVirtualizer.Content>
</ListVirtualizer.Root>
```

`Root` is the scroll container: it receives the ARIA attributes, the `overflow` styles, and the scroll listener. Give it
a fixed height (or width for horizontal lists) so it can scroll. `Content` is the spacer that grows to the total size of
the list. `Item` is positioned absolutely with a `transform`; its size comes from your CSS or from the estimate, so give
your items a height that matches `estimatedSize`, or opt into measurement as shown below.

### List

```tsx
import { ListVirtualizer, useListVirtualizer } from '@ark-ui/react/virtualizer'
import styles from 'styles/virtualizer.module.css'

const items = Array.from({ length: 10000 }, (_, index) => `Item ${index + 1}`)

export const List = () => {
  const virtualizer = useListVirtualizer({
    observeScrollElementSize: true,
    count: items.length,
    estimatedSize: () => 48,
  })

  return (
    <ListVirtualizer.Root value={virtualizer} className={styles.Root}>
      <ListVirtualizer.Content>
        {virtualizer.getVirtualItems().map((item) => (
          <ListVirtualizer.Item
            key={item.key}
            item={item}
            className={`${styles.Item} ${item.index % 2 ? styles.ItemAlt : ''}`}
          >
            {items[item.index]}
          </ListVirtualizer.Item>
        ))}
      </ListVirtualizer.Content>
    </ListVirtualizer.Root>
  )
}
```

### Dynamic Size

When items have different heights, set the `measure` prop on `Item`. The rendered size of each item is then measured and
fed back into the virtualizer, so `estimatedSize` only needs to be a rough guess.

```tsx
import { ListVirtualizer, useListVirtualizer } from '@ark-ui/react/virtualizer'
import styles from 'styles/virtualizer.module.css'

const items = Array.from({ length: 10000 }, (_, index) => ({
  title: `Item ${index + 1}`,
  lines: Array.from({ length: (index % 4) + 1 }, (_, line) => `Line ${line + 1} of item ${index + 1}`),
}))

export const DynamicSize = () => {
  const virtualizer = useListVirtualizer({
    observeScrollElementSize: true,
    count: items.length,
    estimatedSize: () => 64,
  })

  return (
    <ListVirtualizer.Root value={virtualizer} className={styles.Root}>
      <ListVirtualizer.Content>
        {virtualizer.getVirtualItems().map((item) => (
          <ListVirtualizer.Item
            key={item.key}
            item={item}
            measure
            className={`${styles.ItemDynamic} ${item.index % 2 ? styles.ItemAlt : ''}`}
          >
            <strong>{items[item.index].title}</strong>
            {items[item.index].lines.map((line) => (
              <p key={line}>{line}</p>
            ))}
          </ListVirtualizer.Item>
        ))}
      </ListVirtualizer.Content>
    </ListVirtualizer.Root>
  )
}
```

### Horizontal

Set `orientation` to `horizontal` to virtualize along the x-axis. `Content` grows in width and items are translated
horizontally.

```tsx
import { ListVirtualizer, useListVirtualizer } from '@ark-ui/react/virtualizer'
import styles from 'styles/virtualizer.module.css'

const items = Array.from({ length: 10000 }, (_, index) => `${index + 1}`)

export const Horizontal = () => {
  const virtualizer = useListVirtualizer({
    observeScrollElementSize: true,
    count: items.length,
    orientation: 'horizontal',
    estimatedSize: () => 80,
  })

  return (
    <ListVirtualizer.Root value={virtualizer} className={styles.RootHorizontal}>
      <ListVirtualizer.Content>
        {virtualizer.getVirtualItems().map((item) => (
          <ListVirtualizer.Item
            key={item.key}
            item={item}
            className={`${styles.ItemHorizontal} ${item.index % 2 ? styles.ItemAlt : ''}`}
          >
            {items[item.index]}
          </ListVirtualizer.Item>
        ))}
      </ListVirtualizer.Content>
    </ListVirtualizer.Root>
  )
}
```

### Scroll to Index

Use `scrollToIndex` on the virtualizer to jump to an item. It accepts an `align` option (`start`, `center`, `end`, or
`auto`) and a `smooth` option for animated scrolling.

```tsx
import { ListVirtualizer, useListVirtualizer } from '@ark-ui/react/virtualizer'
import styles from 'styles/virtualizer.module.css'

const items = Array.from({ length: 10000 }, (_, index) => `Item ${index + 1}`)

export const ScrollToIndex = () => {
  const virtualizer = useListVirtualizer({
    observeScrollElementSize: true,
    count: items.length,
    estimatedSize: () => 48,
  })

  return (
    <div>
      <div className={styles.Controls}>
        <button type="button" className={styles.Trigger} onClick={() => virtualizer.scrollToIndex(0)}>
          Scroll to top
        </button>
        <button
          type="button"
          className={styles.Trigger}
          onClick={() => virtualizer.scrollToIndex(Math.floor(items.length / 2), { align: 'center' })}
        >
          Scroll to middle
        </button>
        <button type="button" className={styles.Trigger} onClick={() => virtualizer.scrollToIndex(items.length - 1)}>
          Scroll to bottom
        </button>
      </div>
      <ListVirtualizer.Root value={virtualizer} className={styles.Root}>
        <ListVirtualizer.Content>
          {virtualizer.getVirtualItems().map((item) => (
            <ListVirtualizer.Item
              key={item.key}
              item={item}
              className={`${styles.Item} ${item.index % 2 ? styles.ItemAlt : ''}`}
            >
              {items[item.index]}
            </ListVirtualizer.Item>
          ))}
        </ListVirtualizer.Content>
      </ListVirtualizer.Root>
    </div>
  )
}
```

### Grid

The grid virtualizer is row-first: map `getVirtualRows()` to `Row` elements and each row's `columns` to `Cell` elements.
`Cell` reads its row from context, so it only needs the `column`. Rows can opt into height measurement with the
`measure` prop.

```tsx
import { GridVirtualizer, useGridVirtualizer } from '@ark-ui/react/virtualizer'
import styles from 'styles/virtualizer.module.css'

export const Grid = () => {
  const virtualizer = useGridVirtualizer({
    observeScrollElementSize: true,
    rowCount: 1000,
    columnCount: 50,
    estimatedRowSize: () => 40,
    estimatedColumnSize: () => 120,
  })

  return (
    <GridVirtualizer.Root value={virtualizer} className={styles.Grid}>
      <GridVirtualizer.Content>
        {virtualizer.getVirtualRows().map((row) => (
          <GridVirtualizer.Row key={row.row} row={row}>
            {row.columns.map((column) => (
              <GridVirtualizer.Cell key={column.column} column={column} className={styles.Cell}>
                R{row.row + 1}C{column.column + 1}
              </GridVirtualizer.Cell>
            ))}
          </GridVirtualizer.Row>
        ))}
      </GridVirtualizer.Content>
    </GridVirtualizer.Root>
  )
}
```

### Window

The window virtualizer delegates scrolling to the window or the nearest scrollable ancestor, so `Root` does not need a
height or an overflow style. Use it for long pages where the list is part of the document flow.

```tsx
import { WindowVirtualizer, useWindowVirtualizer } from '@ark-ui/react/virtualizer'
import styles from 'styles/virtualizer.module.css'

const items = Array.from({ length: 10000 }, (_, index) => `Item ${index + 1}`)

export const Window = () => {
  const virtualizer = useWindowVirtualizer({
    count: items.length,
    estimatedSize: () => 48,
  })

  return (
    <WindowVirtualizer.Root value={virtualizer} className={styles.Window}>
      <WindowVirtualizer.Content>
        {virtualizer.getVirtualItems().map((item) => (
          <WindowVirtualizer.Item
            key={item.key}
            item={item}
            className={`${styles.Item} ${item.index % 2 ? styles.ItemAlt : ''}`}
          >
            {items[item.index]}
          </WindowVirtualizer.Item>
        ))}
      </WindowVirtualizer.Content>
    </WindowVirtualizer.Root>
  )
}
```

### Using the Context

`Context` exposes the virtualizer to child components, which is useful for rendering derived state such as the visible
range.

```tsx
<ListVirtualizer.Root value={virtualizer}>
  <ListVirtualizer.Context>
    {(virtualizer) => <span>{virtualizer.getVirtualItems().length} items rendered</span>}
  </ListVirtualizer.Context>
</ListVirtualizer.Root>
```

### Composing with other components

The virtualizer parts compose with any Ark component through the `render` prop. Render the component's scroll container
as `ListVirtualizer.Root` and each of its items as `ListVirtualizer.Item`; the component's own props (roles, ids,
handlers) win over the virtualizer's, and the virtualizer adds the positioning and the `aria-posinset` / `aria-setsize`
attributes.

```tsx
<Combobox.List render={<ListVirtualizer.Root value={virtualizer} />}>
  <ListVirtualizer.Content>
    {virtualizer.getVirtualItems().map((virtualItem) => {
      const item = collection.items[virtualItem.index]
      return (
        <Combobox.Item key={item.value} item={item} render={<ListVirtualizer.Item item={virtualItem} />}>
          <Combobox.ItemText>{item.label}</Combobox.ItemText>
        </Combobox.Item>
      )
    })}
  </ListVirtualizer.Content>
</Combobox.List>
```

Pass the component's `scrollToIndexFn` through to `virtualizer.scrollToIndex` so keyboard navigation reaches items that
are not mounted. See the virtualized examples for [Select](/docs/components/select#virtualized),
[Combobox](/docs/components/combobox#virtualized), [Listbox](/docs/components/listbox#virtualized), and
[Tree View](/docs/components/tree-view#virtualized).

## API Reference

### GridCell

#### Props

**`column`**
Type: `VirtualColumn`
Required: true
Default Value: `undefined`
Description: The virtual column to render within the current row.

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.

### GridContent

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.

### GridRoot

#### Props

**`value`**
Type: `UseGridVirtualizerReturn`
Required: true
Default Value: `undefined`
Description: The virtualizer instance returned by `useGridVirtualizer`.

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.

### GridRow

#### Props

**`row`**
Type: `VirtualRow`
Required: true
Default Value: `undefined`
Description: The virtual row to render.

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`measure`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether to measure the rendered height of the row and use it instead of the estimate.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.

### ListContent

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.

### ListItem

#### Props

**`item`**
Type: `VirtualItem`
Required: true
Default Value: `undefined`
Description: The virtual item to render.

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`measure`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether to measure the rendered size of the item and use it instead of the estimate.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.

### ListRoot

#### Props

**`value`**
Type: `UseListVirtualizerReturn`
Required: true
Default Value: `undefined`
Description: The virtualizer instance returned by `useListVirtualizer`.

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.

### WindowContent

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.

### WindowItem

#### Props

**`item`**
Type: `VirtualItem`
Required: true
Default Value: `undefined`
Description: The virtual item to render.

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`measure`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether to measure the rendered size of the item and use it instead of the estimate.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.

### WindowRoot

#### Props

**`value`**
Type: `UseWindowVirtualizerReturn`
Required: true
Default Value: `undefined`
Description: The virtualizer instance returned by `useWindowVirtualizer`.

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.
