---
title: List
description: Headless collection renderer with polymorphic roots and caller-controlled virtualization.
sidebar:
  order: 11
search:
  tags: [list, collection, virtualization, headless]
---

# List

> Headless collection renderer with polymorphic roots and caller-controlled virtualization.

## Import

```tsx
import { List } from 'moraine'
import { useListVirtualizer } from 'moraine/utils'
```

`List` owns collection traversal and the scroll-element bridge used by virtual renderers. Item markup, semantics, styling, filtering, focus, and selection remain under caller control.

## Examples

### Object List

Render arbitrary objects with a semantic `ul` root, `role="list"`, and `data-slot="root"` by default. Use `as` and native root attributes when another element or role is required; explicit attributes override the defaults.

```tsx
function Basic() {
  return (
    <List
      items={JOBS}
      aria-label="Open positions"
      class="border border-border rounded-md divide-border divide-y"
      itemRender={(context) => (
        <li class="p-3 flex gap-3 items-center justify-between">
          <span class="flex flex-col min-w-0">
            <span class="font-medium">{context.item.title}</span>
            <span class="text-sm text-muted-foreground">{context.item.team}</span>
          </span>
          <span class="text-xs text-muted-foreground shrink-0">{context.item.location}</span>
        </li>
      )}
    />
  )
}
```

### Virtualization

Import `useListVirtualizer` from `moraine/utils`. It provides a ready-to-use `virtualRender`, automatically connects the scroll element, positions rows, and forwards dynamic measurement refs. Configure `estimateSize`, a stable `getItemKey`, and optional TanStack Virtual settings such as `gap` and `overscan`.

The built-in adapter requires `@tanstack/virtual-core` as an optional peer dependency. Install a compatible version in your application before using it.

```bash
bun add @tanstack/virtual-core
```

Provide a custom `virtualRender` component when the built-in absolute row layout is not suitable.

```tsx
function Virtualization() {
  const ITEMS = Array.from({ length: 10_000 }, (_, index) => ({
    id: index + 1,
    label: `Result ${index + 1}`,
  }))

  type Item = (typeof ITEMS)[number]

  const virtualizer = useListVirtualizer<Item, HTMLElement, HTMLDivElement>({
    estimateSize: () => 36,
    getItemKey: (item) => item.id,
    overscan: 8,
  })

  return (
    <List
      as="div"
      items={ITEMS}
      virtualRender={virtualizer.virtualRender}
      role="list"
      aria-label="Virtual results"
      class="border border-border rounded-md h-72 w-full overflow-y-auto"
      itemRender={(context) => (
        <div {...context.props} role="listitem">
          <div class="px-3 border-b border-border flex h-9 items-center">{context.item.label}</div>
        </div>
      )}
    />
  )
}
```

### Dynamic Heights

For variable-height rows, provide a reasonable estimate and let the built-in renderer measure connected rows. TanStack Virtual observes later content size changes and recalculates positions; `gap` remains consistent between measured rows.

```tsx
function DynamicHeight() {
  const ITEMS = Array.from({ length: 1_000 }, (_, index) => ({
    id: index + 1,
    label: `Result ${index + 1}`,
    details: Array.from(
      { length: (index % 4) + 1 },
      (_, detailIndex) => `Detail line ${detailIndex + 1} for result ${index + 1}.`,
    ),
  }))

  type Item = (typeof ITEMS)[number]

  const virtualizer = useListVirtualizer<Item, HTMLElement, HTMLDivElement>({
    estimateSize: () => 96,
    getItemKey: (item) => item.id,
    gap: 8,
    overscan: 8,
  })

  return (
    <List
      as="div"
      items={ITEMS}
      virtualRender={virtualizer.virtualRender}
      role="list"
      aria-label="Variable-height results"
      class="py-2 border border-border rounded-md h-80 w-full overflow-y-auto"
      itemRender={(context) => (
        <div {...context.props} role="listitem">
          <div class="mx-2 px-3 py-2 border border-border rounded-md">
            <div class="font-medium">{context.item.label}</div>
            <div class="text-sm text-muted-foreground">
              <For each={context.item.details}>
                {(detail) => <span class="block">{detail}</span>}
              </For>
            </div>
          </div>
        </div>
      )}
    />
  )
}
```

## API Reference

### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| as | T \| undefined | ul | Root element or component. |
| class | ClassValue | — | Class applied to the component root or trigger element. |
| classes | ListT.Classes \| undefined | — | — |
| itemRender* | ComponentOrElement<ListT.ItemRenderProps<TItem, TItemElement>> | — | Renders one collection item. |
| items | readonly TItem[] \| undefined | — | Reactive collection rendered by the list. |
| ref | JSX.HTMLElementTags[T] extends { ref?: infer Ref; } ? Ref : never \| undefined | — | — |
| style | JSX.CSSProperties \| undefined | — | — |
| styles | ListT.Styles \| undefined | — | — |
| virtualRender | Component<ListT.VirtualRenderProps<TItem, HTMLElement, TItemElement>> \| undefined | — | Replaces normal iteration with caller-controlled virtual rendering. |

### ARIA

Accessibility attributes and roles emitted by the component markup.

| Attribute | Type | Description |
| --- | --- | --- |
| role | string | Defines the semantic role exposed to assistive technology. |

### Data Attributes

State and slot attributes exposed for styling hooks and selectors.

| Attribute | Type | Description |
| --- | --- | --- |
| data-slot | string | Identifies the rendered slot for styling hooks and selectors. |
