# Async List (SOLID)

The `useAsyncList` hook manages asynchronous data operations for list collections. It provides a comprehensive solution
for loading, filtering, sorting, and paginating data with built-in loading states, error handling, and request
cancellation.

```tsx
import { useAsyncList } from '@ark-ui/react/collection'

const list = useAsyncList<User>({
  async load({ signal }) {
    const response = await fetch('/api/users', { signal })
    const users = await response.json()
    return { items: users }
  },
})

console.log(list.items) // User[]
console.log(list.loading) // boolean
console.log(list.error) // Error | null
```

## Examples

### Loading Data

Load data asynchronously from an API using the `load` function and update the list when the data is ready.

**Example: async-list/reload**

#### React

```tsx
import { useAsyncList } from '@ark-ui/react/collection'
import { LoaderIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/async-list.module.css'

interface Quote {
  id: number
  quote: string
  author: string
}

export const Reload = () => {
  const list = useAsyncList<Quote>({
    autoReload: true,
    async load() {
      const response = await fetch(`https://dummyjson.com/quotes?limit=4&skip=${Math.floor(Math.random() * 50)}`)

      if (!response.ok) {
        throw new Error('Failed to fetch quotes')
      }

      const data = await response.json()
      return { items: data.quotes }
    },
  })

  return (
    <div className={styles.Root}>
      <div className={styles.Header}>
        <button className={button.Root} onClick={() => list.reload()} disabled={list.loading}>
          {list.loading ? (
            <>
              <LoaderIcon className={styles.Spinner} /> Loading
            </>
          ) : (
            'Reload Quotes'
          )}
        </button>
      </div>

      {list.error && <div className={styles.Error}>Error: {list.error.message}</div>}

      <div className={styles.ItemGroup}>
        {list.items.map((quote) => (
          <div key={quote.id} className={styles.Item}>
            <div className={styles.ItemContent}>
              <div className={styles.ItemDescription}>"{quote.quote}"</div>
              <div className={styles.ItemMeta}>— {quote.author}</div>
            </div>
          </div>
        ))}
      </div>
    </div>
  )
}

```

#### Solid

```tsx
import { useAsyncList } from '@ark-ui/solid/collection'
import { LoaderIcon } from 'lucide-solid'
import { For } from 'solid-js'
import button from 'styles/button.module.css'
import styles from 'styles/async-list.module.css'

interface Quote {
  id: number
  quote: string
  author: string
}

export const Reload = () => {
  const list = useAsyncList<Quote>({
    autoReload: true,
    async load() {
      const response = await fetch(`https://dummyjson.com/quotes?limit=4&skip=${Math.floor(Math.random() * 50)}`)

      if (!response.ok) {
        throw new Error('Failed to fetch quotes')
      }

      const data = await response.json()
      return { items: data.quotes }
    },
  })

  return (
    <div class={styles.Root}>
      <div class={styles.Header}>
        <button class={button.Root} onClick={() => list().reload()} disabled={list().loading}>
          {list().loading ? (
            <>
              <LoaderIcon class={styles.Spinner} /> Loading
            </>
          ) : (
            'Reload Quotes'
          )}
        </button>
      </div>

      {list().error && <div class={styles.Error}>Error: {list().error.message}</div>}

      <div class={styles.ItemGroup}>
        <For each={list().items}>
          {(quote) => (
            <div class={styles.Item}>
              <div class={styles.ItemContent}>
                <div class={styles.ItemDescription}>"{quote.quote}"</div>
                <div class={styles.ItemMeta}>— {quote.author}</div>
              </div>
            </div>
          )}
        </For>
      </div>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { useAsyncList } from '@ark-ui/vue/collection'
import { LoaderIcon } from 'lucide-vue-next'
import button from 'styles/button.module.css'
import styles from 'styles/async-list.module.css'

interface Quote {
  id: number
  quote: string
  author: string
}

const list = useAsyncList<Quote>({
  autoReload: true,
  async load() {
    const response = await fetch(`https://dummyjson.com/quotes?limit=4&skip=${Math.floor(Math.random() * 50)}`)

    if (!response.ok) {
      throw new Error('Failed to fetch quotes')
    }

    const data = await response.json()
    return { items: data.quotes }
  },
})
</script>

<template>
  <div :class="styles.Root">
    <div :class="styles.Header">
      <button :class="button.Root" @click="list.reload()" :disabled="list.loading">
        <template v-if="list.loading">
          <LoaderIcon :class="styles.Spinner" />
          Loading
        </template>
        <template v-else>Reload Quotes</template>
      </button>
    </div>

    <div v-if="list.error" :class="styles.Error">Error: {{ list.error.message }}</div>

    <div :class="styles.ItemGroup">
      <div v-for="quote in list.items" :key="quote.id" :class="styles.Item">
        <div :class="styles.ItemContent">
          <div :class="styles.ItemDescription">"{{ quote.quote }}"</div>
          <div :class="styles.ItemMeta">— {{ quote.author }}</div>
        </div>
      </div>
    </div>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { useAsyncList } from '@ark-ui/svelte/collection'
  import { LoaderIcon } from 'lucide-svelte'
  import button from 'styles/button.module.css'
  import styles from 'styles/async-list.module.css'

  interface Quote {
    id: number
    quote: string
    author: string
  }

  const list = useAsyncList<Quote>({
    autoReload: true,
    async load() {
      const response = await fetch(`https://dummyjson.com/quotes?limit=4&skip=${Math.floor(Math.random() * 50)}`)

      if (!response.ok) {
        throw new Error('Failed to fetch quotes')
      }

      const data = await response.json()
      return { items: data.quotes }
    },
  })
</script>

<div class={styles.Root}>
  <div class={styles.Header}>
    <button class={button.Root} onclick={() => list().reload()} disabled={list().loading}>
      {#if list().loading}
        <LoaderIcon class={styles.Spinner} /> Loading
      {:else}
        Reload Quotes
      {/if}
    </button>
  </div>

  {#if list().error}
    <div class={styles.Error}>Error: {list().error.message}</div>
  {/if}

  <div class={styles.ItemGroup}>
    {#each list().items as quote}
      <div class={styles.Item}>
        <div class={styles.ItemContent}>
          <div class={styles.ItemDescription}>"{quote.quote}"</div>
          <div class={styles.ItemMeta}>— {quote.author}</div>
        </div>
      </div>
    {/each}
  </div>
</div>

```

### Infinite Loading

Implement pagination by returning a cursor that indicates more data is available.

**Example: async-list/infinite-loading**

#### React

```tsx
import { useAsyncList } from '@ark-ui/react/collection'
import { LoaderIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/async-list.module.css'

const LIMIT = 4

interface Post {
  userId: number
  id: number
  title: string
  body: string
}

export const InfiniteLoading = () => {
  const list = useAsyncList<Post, number>({
    autoReload: true,
    async load({ cursor }) {
      const page = cursor || 1
      const start = (page - 1) * LIMIT

      const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_start=${start}&_limit=${LIMIT}`)

      if (!response.ok) {
        throw new Error('Failed to fetch posts')
      }

      const posts: Post[] = await response.json()
      const hasNextPage = posts.length === LIMIT

      return {
        items: posts,
        cursor: hasNextPage ? page + 1 : undefined,
      }
    },
  })

  return (
    <div className={styles.Root}>
      <div className={styles.Header}>
        <span className={styles.Status}>
          Loaded {list.items.length} posts
          {list.cursor && ` (more available)`}
        </span>
        {list.cursor && (
          <button className={button.Root} onClick={() => list.loadMore()} disabled={list.loading}>
            {list.loading ? (
              <>
                <LoaderIcon className={styles.Spinner} /> Loading
              </>
            ) : (
              'Load More'
            )}
          </button>
        )}
      </div>

      {list.error && <div className={styles.Error}>Error: {list.error.message}</div>}

      <div className={styles.ItemGroup}>
        {list.items.map((post, index) => (
          <div key={post.id} className={styles.Item}>
            <div className={styles.ItemContent}>
              <div className={styles.ItemTitle}>
                {index + 1}. {post.title}
              </div>
              <div className={styles.ItemDescription}>{post.body}</div>
            </div>
          </div>
        ))}
      </div>
    </div>
  )
}

```

#### Solid

```tsx
import { useAsyncList } from '@ark-ui/solid/collection'
import { LoaderIcon } from 'lucide-solid'
import { For } from 'solid-js'
import button from 'styles/button.module.css'
import styles from 'styles/async-list.module.css'

const LIMIT = 4

interface Post {
  userId: number
  id: number
  title: string
  body: string
}

export const InfiniteLoading = () => {
  const list = useAsyncList<Post, number>({
    autoReload: true,
    async load({ cursor }) {
      const page = cursor || 1
      const start = (page - 1) * LIMIT

      const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_start=${start}&_limit=${LIMIT}`)

      if (!response.ok) {
        throw new Error('Failed to fetch posts')
      }

      const posts: Post[] = await response.json()
      const hasNextPage = posts.length === LIMIT

      return {
        items: posts,
        cursor: hasNextPage ? page + 1 : undefined,
      }
    },
  })

  return (
    <div class={styles.Root}>
      <div class={styles.Header}>
        <span class={styles.Status}>
          Loaded {list().items.length} posts
          {list().cursor && ` (more available)`}
        </span>
        {list().cursor && (
          <button class={button.Root} onClick={() => list().loadMore()} disabled={list().loading}>
            {list().loading ? (
              <>
                <LoaderIcon class={styles.Spinner} /> Loading
              </>
            ) : (
              'Load More'
            )}
          </button>
        )}
      </div>

      {list().error && <div class={styles.Error}>Error: {list().error.message}</div>}

      <div class={styles.ItemGroup}>
        <For each={list().items}>
          {(post, index) => (
            <div class={styles.Item}>
              <div class={styles.ItemContent}>
                <div class={styles.ItemTitle}>
                  {index() + 1}. {post.title}
                </div>
                <div class={styles.ItemDescription}>{post.body}</div>
              </div>
            </div>
          )}
        </For>
      </div>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { useAsyncList } from '@ark-ui/vue/collection'
import { LoaderIcon } from 'lucide-vue-next'
import button from 'styles/button.module.css'
import styles from 'styles/async-list.module.css'

const LIMIT = 5

interface Post {
  userId: number
  id: number
  title: string
  body: string
}

const list = useAsyncList<Post, number>({
  autoReload: true,
  async load({ cursor }) {
    const page = cursor || 1
    const start = (page - 1) * LIMIT

    const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_start=${start}&_limit=${LIMIT}`)

    if (!response.ok) {
      throw new Error('Failed to fetch posts')
    }

    const posts: Post[] = await response.json()
    const hasNextPage = posts.length === LIMIT

    return {
      items: posts,
      cursor: hasNextPage ? page + 1 : undefined,
    }
  },
})
</script>

<template>
  <div :class="styles.Root">
    <div :class="styles.Header">
      <button v-if="list.cursor" :class="button.Root" @click="list.loadMore()" :disabled="list.loading">
        <template v-if="list.loading">
          <LoaderIcon :class="styles.Spinner" />
          Loading
        </template>
        <template v-else>Load More</template>
      </button>
    </div>

    <div :class="styles.Status">
      Loaded {{ list.items.length }} posts
      <span v-if="list.cursor">(more available)</span>
    </div>

    <div v-if="list.error" :class="styles.Error">Error: {{ list.error.message }}</div>

    <div :class="styles.ItemGroup">
      <div v-for="(post, index) in list.items" :key="post.id" :class="styles.Item">
        <div :class="styles.ItemContent">
          <div :class="styles.ItemTitle">{{ index + 1 }}. {{ post.title }}</div>
          <div :class="styles.ItemDescription">{{ post.body }}</div>
        </div>
      </div>
    </div>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { useAsyncList } from '@ark-ui/svelte/collection'
  import { LoaderIcon } from 'lucide-svelte'
  import button from 'styles/button.module.css'
  import styles from 'styles/async-list.module.css'

  const LIMIT = 4

  interface Post {
    userId: number
    id: number
    title: string
    body: string
  }

  const list = useAsyncList<Post, number>({
    autoReload: true,
    async load({ cursor }) {
      const page = cursor || 1
      const start = (page - 1) * LIMIT

      const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_start=${start}&_limit=${LIMIT}`)

      if (!response.ok) {
        throw new Error('Failed to fetch posts')
      }

      const posts: Post[] = await response.json()
      const hasNextPage = posts.length === LIMIT

      return {
        items: posts,
        cursor: hasNextPage ? page + 1 : undefined,
      }
    },
  })
</script>

<div class={styles.Root}>
  <div class={styles.Header}>
    <span class={styles.Status}>
      Loaded {list().items.length} posts
      {#if list().cursor}(more available){/if}
    </span>
    {#if list().cursor}
      <button class={button.Root} onclick={() => list().loadMore()} disabled={list().loading}>
        {#if list().loading}
          <LoaderIcon class={styles.Spinner} /> Loading
        {:else}
          Load More
        {/if}
      </button>
    {/if}
  </div>

  {#if list().error}
    <div class={styles.Error}>Error: {list().error.message}</div>
  {/if}

  <div class={styles.ItemGroup}>
    {#each list().items as post, index}
      <div class={styles.Item}>
        <div class={styles.ItemContent}>
          <div class={styles.ItemTitle}>{index + 1}. {post.title}</div>
          <div class={styles.ItemDescription}>{post.body}</div>
        </div>
      </div>
    {/each}
  </div>
</div>

```

### Filtering

Filter data based on user input with automatic debouncing and loading states.

**Example: async-list/filter**

#### React

```tsx
import { useAsyncList } from '@ark-ui/react/collection'
import { LoaderIcon } from 'lucide-react'
import field from 'styles/field.module.css'
import styles from 'styles/async-list.module.css'

const LIMIT = 4

interface User {
  id: number
  name: string
  email: string
  department: string
  role: string
}

export const Filter = () => {
  const list = useAsyncList<User>({
    initialItems: mockUsers.slice(0, LIMIT),
    async load({ filterText }) {
      await delay(500)

      if (!filterText) {
        return { items: mockUsers.slice(0, LIMIT) }
      }

      const filtered = mockUsers.filter(
        (user) =>
          user.name.toLowerCase().includes(filterText.toLowerCase()) ||
          user.email.toLowerCase().includes(filterText.toLowerCase()),
      )

      return { items: filtered.slice(0, LIMIT) }
    },
  })

  return (
    <div className={styles.Root}>
      <div className={styles.Header}>
        <input
          className={field.Input}
          type="text"
          placeholder="Search users..."
          value={list.filterText}
          onChange={(e) => list.setFilterText(e.target.value)}
        />
        {list.loading && (
          <span className={styles.Loading}>
            <LoaderIcon className={styles.Spinner} /> Searching
          </span>
        )}
      </div>

      {list.error && <div className={styles.Error}>Error: {list.error.message}</div>}

      <div className={styles.ItemGroup}>
        {list.items.map((user) => (
          <div key={user.id} className={styles.Item}>
            <div className={styles.ItemContent}>
              <div className={styles.ItemTitle}>{user.name}</div>
              <div className={styles.ItemDescription}>{user.email}</div>
              <div className={styles.ItemMeta}>
                {user.department} • {user.role}
              </div>
            </div>
          </div>
        ))}
      </div>

      {list.items.length === 0 && !list.loading && <div className={styles.Empty}>No results found</div>}
    </div>
  )
}

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

const mockUsers: User[] = [
  { id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
  { id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
  { id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
  { id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
  { id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
  { id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
  { id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
  { id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
  { id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
  { id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
  { id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
  { id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
  { id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
  { id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
  { id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
  { id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
  { id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
  { id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
  { id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
  { id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
]

```

#### Solid

```tsx
import { useAsyncList } from '@ark-ui/solid/collection'
import { LoaderIcon } from 'lucide-solid'
import { For } from 'solid-js'
import field from 'styles/field.module.css'
import styles from 'styles/async-list.module.css'

const LIMIT = 4

interface User {
  id: number
  name: string
  email: string
  department: string
  role: string
}

export const Filter = () => {
  const list = useAsyncList<User>({
    initialItems: mockUsers.slice(0, LIMIT),
    async load({ filterText }: { filterText?: string }) {
      await delay(500)

      if (!filterText) {
        return { items: mockUsers.slice(0, LIMIT) }
      }

      const filtered = mockUsers.filter(
        (user) =>
          user.name.toLowerCase().includes(filterText.toLowerCase()) ||
          user.email.toLowerCase().includes(filterText.toLowerCase()),
      )

      return { items: filtered.slice(0, LIMIT) }
    },
  })

  return (
    <div class={styles.Root}>
      <div class={styles.Header}>
        <input
          class={field.Input}
          type="text"
          placeholder="Search users..."
          value={list().filterText}
          onInput={(e) => list().setFilterText(e.target.value)}
        />
        {list().loading && (
          <span class={styles.Loading}>
            <LoaderIcon class={styles.Spinner} /> Searching
          </span>
        )}
      </div>

      {list().error && <div class={styles.Error}>Error: {list().error.message}</div>}

      <div class={styles.ItemGroup}>
        <For each={list().items}>
          {(user) => (
            <div class={styles.Item}>
              <div class={styles.ItemContent}>
                <div class={styles.ItemTitle}>{user.name}</div>
                <div class={styles.ItemDescription}>{user.email}</div>
                <div class={styles.ItemMeta}>
                  {user.department} • {user.role}
                </div>
              </div>
            </div>
          )}
        </For>
      </div>

      {list().items.length === 0 && !list().loading && <div class={styles.Empty}>No results found</div>}
    </div>
  )
}

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

const mockUsers: User[] = [
  { id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
  { id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
  { id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
  { id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
  { id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
  { id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
  { id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
  { id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
  { id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
  { id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
  { id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
  { id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
  { id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
  { id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
  { id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
  { id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
  { id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
  { id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
  { id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
  { id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
]

```

#### Vue

```vue
<script setup lang="ts">
import { useAsyncList } from '@ark-ui/vue/collection'
import { LoaderIcon } from 'lucide-vue-next'
import field from 'styles/field.module.css'
import styles from 'styles/async-list.module.css'

const LIMIT = 4

interface User {
  id: number
  name: string
  email: string
  department: string
  role: string
}

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

const mockUsers: User[] = [
  { id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
  { id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
  { id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
  { id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
  { id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
  { id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
  { id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
  { id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
  { id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
  { id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
  { id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
  { id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
  { id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
  { id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
  { id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
  { id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
  { id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
  { id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
  { id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
  { id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
]

const list = useAsyncList<User>({
  initialItems: mockUsers.slice(0, LIMIT),
  async load({ filterText }: { filterText?: string } = {}) {
    await delay(500)

    if (!filterText) {
      return { items: mockUsers.slice(0, LIMIT) }
    }

    const filtered = mockUsers.filter(
      (user) =>
        user.name.toLowerCase().includes(filterText.toLowerCase()) ||
        user.email.toLowerCase().includes(filterText.toLowerCase()),
    )

    return { items: filtered.slice(0, LIMIT) }
  },
})
</script>

<template>
  <div :class="styles.Root">
    <div :class="styles.Header">
      <input
        :class="field.Input"
        type="text"
        placeholder="Search users..."
        :value="list.filterText"
        @input="list.setFilterText(($event.currentTarget as HTMLInputElement).value ?? '')"
      />
      <span v-if="list.loading" :class="styles.Loading">
        <LoaderIcon :class="styles.Spinner" />
        Searching
      </span>
    </div>

    <div v-if="list.error" :class="styles.Error">Error: {{ list.error.message }}</div>

    <div :class="styles.ItemGroup">
      <div v-for="user in list.items" :key="user.id" :class="styles.Item">
        <div :class="styles.ItemContent">
          <div :class="styles.ItemTitle">{{ user.name }}</div>
          <div :class="styles.ItemDescription">{{ user.email }}</div>
          <div :class="styles.ItemMeta">{{ user.department }} • {{ user.role }}</div>
        </div>
      </div>
    </div>

    <div v-if="list.items.length === 0 && !list.loading" :class="styles.Empty">No results found</div>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { useAsyncList } from '@ark-ui/svelte/collection'
  import { LoaderIcon } from 'lucide-svelte'
  import field from 'styles/field.module.css'
  import styles from 'styles/async-list.module.css'

  const LIMIT = 4

  interface User {
    id: number
    name: string
    email: string
    department: string
    role: string
  }

  const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

  const mockUsers: User[] = [
    { id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
    { id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
    { id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
    { id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
    { id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
    { id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
    { id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
    { id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
    { id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
    { id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
    { id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
    { id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
    { id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
    { id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
    { id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
    { id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
    { id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
    { id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
    { id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
    { id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
  ]

  const list = useAsyncList<User>({
    initialItems: mockUsers.slice(0, LIMIT),
    async load({ filterText }: { filterText?: string } = {}) {
      await delay(500)

      if (!filterText) {
        return { items: mockUsers.slice(0, LIMIT) }
      }

      const filtered = mockUsers.filter(
        (user) =>
          user.name.toLowerCase().includes(filterText.toLowerCase()) ||
          user.email.toLowerCase().includes(filterText.toLowerCase()),
      )

      return { items: filtered.slice(0, LIMIT) }
    },
  })
</script>

<div class={styles.Root}>
  <div class={styles.Header}>
    <input
      class={field.Input}
      type="text"
      placeholder="Search users..."
      value={list().filterText}
      oninput={(e) => list().setFilterText(e.currentTarget.value)}
    />
    {#if list().loading}
      <span class={styles.Loading}>
        <LoaderIcon class={styles.Spinner} /> Searching
      </span>
    {/if}
  </div>

  {#if list().error}
    <div class={styles.Error}>Error: {list().error.message}</div>
  {/if}

  <div class={styles.ItemGroup}>
    {#each list().items as user}
      <div class={styles.Item}>
        <div class={styles.ItemContent}>
          <div class={styles.ItemTitle}>{user.name}</div>
          <div class={styles.ItemDescription}>{user.email}</div>
          <div class={styles.ItemMeta}>
            {user.department} • {user.role}
          </div>
        </div>
      </div>
    {/each}
  </div>

  {#if list().items.length === 0 && !list().loading}
    <div class={styles.Empty}>No results found</div>
  {/if}
</div>

```

### Sorting (Client-side)

Sort data on the client side after loading from the server.

**Example: async-list/sort-client-side**

#### React

```tsx
import { useAsyncList } from '@ark-ui/react/collection'
import { useCollator } from '@ark-ui/react/locale'
import { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon, LoaderIcon } from 'lucide-react'
import styles from 'styles/async-list.module.css'

interface User {
  id: number
  name: string
  username: string
  email: string
  phone: string
  website: string
}

export const SortClientSide = () => {
  const collator = useCollator()

  const list = useAsyncList<User>({
    autoReload: true,
    load: async () => {
      const response = await fetch('https://jsonplaceholder.typicode.com/users?_limit=5')
      const data = await response.json()
      return { items: data }
    },
    sort({ items, descriptor }) {
      return {
        items: items.sort((a, b) => {
          const { column, direction } = descriptor
          let cmp = collator.compare(String(a[column]), String(b[column]))
          if (direction === 'descending') {
            cmp *= -1
          }
          return cmp
        }),
      }
    },
  })

  const handleSort = (column: keyof User) => {
    const currentSort = list.sortDescriptor
    let direction: 'ascending' | 'descending' = 'ascending'

    if (currentSort?.column === column && currentSort.direction === 'ascending') {
      direction = 'descending'
    }

    list.sort({ column, direction })
  }

  const getSortIcon = (column: keyof User) => {
    const current = list.sortDescriptor
    if (current?.column !== column) return <ArrowUpDownIcon />
    return current.direction === 'ascending' ? <ArrowUpIcon /> : <ArrowDownIcon />
  }

  const descriptor = list.sortDescriptor

  return (
    <div className={styles.Root}>
      {list.loading && (
        <div className={styles.Loading}>
          <LoaderIcon className={styles.Spinner} /> Loading
        </div>
      )}
      {list.error && <div className={styles.Error}>Error: {list.error.message}</div>}
      <div className={styles.Status}>
        Sorted by: {descriptor ? `${descriptor.column} (${descriptor.direction})` : 'none'}
      </div>

      <table className={styles.Table}>
        <thead>
          <tr>
            {[
              { key: 'name', label: 'Name' },
              { key: 'username', label: 'Username' },
              { key: 'email', label: 'Email' },
            ].map(({ key, label }) => (
              <th key={key} onClick={() => handleSort(key as keyof User)}>
                {label} {getSortIcon(key as keyof User)}
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {list.items.map((user) => (
            <tr key={user.id}>
              <td>{user.name}</td>
              <td>{user.username}</td>
              <td>{user.email}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  )
}

```

#### Solid

```tsx
import { useAsyncList } from '@ark-ui/solid/collection'
import { useCollator } from '@ark-ui/solid/locale'
import { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon, LoaderIcon } from 'lucide-solid'
import { For } from 'solid-js'
import styles from 'styles/async-list.module.css'

interface User {
  id: number
  name: string
  username: string
  email: string
  phone: string
  website: string
}

export const SortClientSide = () => {
  const collator = useCollator()

  const list = useAsyncList<User>({
    autoReload: true,
    load: async () => {
      const response = await fetch('https://jsonplaceholder.typicode.com/users?_limit=5')
      const data = await response.json()
      return { items: data }
    },
    sort({ items, descriptor }) {
      return {
        items: items.sort((a, b) => {
          const { column, direction } = descriptor
          let cmp = collator().compare(String(a[column]), String(b[column]))
          if (direction === 'descending') {
            cmp *= -1
          }
          return cmp
        }),
      }
    },
  })

  const handleSort = (column: keyof User) => {
    const currentSort = list().sortDescriptor
    let direction: 'ascending' | 'descending' = 'ascending'

    if (currentSort?.column === column && currentSort.direction === 'ascending') {
      direction = 'descending'
    }

    list().sort({ column, direction })
  }

  const getSortIcon = (column: keyof User) => {
    const current = list().sortDescriptor
    if (current?.column !== column) return <ArrowUpDownIcon />
    return current.direction === 'ascending' ? <ArrowUpIcon /> : <ArrowDownIcon />
  }

  const descriptor = () => list().sortDescriptor

  return (
    <div class={styles.Root}>
      {list().loading && (
        <div class={styles.Loading}>
          <LoaderIcon class={styles.Spinner} /> Loading
        </div>
      )}
      {list().error && <div class={styles.Error}>Error: {list().error.message}</div>}
      <div class={styles.Status}>
        Sorted by: {descriptor() ? `${descriptor()?.column} (${descriptor()?.direction})` : 'none'}
      </div>

      <table class={styles.Table}>
        <thead>
          <tr>
            <For
              each={[
                { key: 'name', label: 'Name' },
                { key: 'username', label: 'Username' },
                { key: 'email', label: 'Email' },
              ]}
            >
              {({ key, label }) => (
                <th onClick={() => handleSort(key as keyof User)}>
                  {label} {getSortIcon(key as keyof User)}
                </th>
              )}
            </For>
          </tr>
        </thead>
        <tbody>
          <For each={list().items}>
            {(user) => (
              <tr>
                <td>{user.name}</td>
                <td>{user.username}</td>
                <td>{user.email}</td>
              </tr>
            )}
          </For>
        </tbody>
      </table>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { useAsyncList } from '@ark-ui/vue/collection'
import { useCollator } from '@ark-ui/vue/locale'
import { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon, LoaderIcon } from 'lucide-vue-next'
import { computed } from 'vue'
import styles from 'styles/async-list.module.css'

interface User {
  id: number
  name: string
  username: string
  email: string
  phone: string
  website: string
}

const collator = useCollator()

const list = useAsyncList<User>({
  autoReload: true,
  load: async () => {
    const response = await fetch('https://jsonplaceholder.typicode.com/users?_limit=5')
    const data = await response.json()
    return { items: data }
  },
  sort({ items, descriptor }) {
    return {
      items: items.sort((a, b) => {
        const { column, direction } = descriptor
        let cmp = collator.value.compare(String(a[column]), String(b[column]))
        if (direction === 'descending') {
          cmp *= -1
        }
        return cmp
      }),
    }
  },
})

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'username', label: 'Username' },
  { key: 'email', label: 'Email' },
]

const handleSort = (column: keyof User) => {
  const currentSort = list.value.sortDescriptor
  let direction: 'ascending' | 'descending' = 'ascending'

  if (currentSort?.column === column && currentSort.direction === 'ascending') {
    direction = 'descending'
  }

  list.value.sort({ column, direction })
}

const descriptor = computed(() => list.value.sortDescriptor)
</script>

<template>
  <div :class="styles.Root">
    <div v-if="list.loading" :class="styles.Loading">
      <LoaderIcon :class="styles.Spinner" />
      Loading
    </div>
    <div v-if="list.error" :class="styles.Error">Error: {{ list.error.message }}</div>
    <div :class="styles.Status">
      Sorted by: {{ descriptor ? `${descriptor.column} (${descriptor.direction})` : 'none' }}
    </div>

    <table :class="styles.Table">
      <thead>
        <tr>
          <th v-for="{ key, label } in columns" :key="key" @click="handleSort(key as keyof User)">
            {{ label }}
            <template v-if="list.sortDescriptor?.column === key">
              <ArrowUpIcon v-if="list.sortDescriptor?.direction === 'ascending'" />
              <ArrowDownIcon v-else />
            </template>
            <ArrowUpDownIcon v-else />
          </th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="user in list.items" :key="user.id">
          <td>{{ user.name }}</td>
          <td>{{ user.username }}</td>
          <td>{{ user.email }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { useAsyncList } from '@ark-ui/svelte/collection'
  import { useCollator } from '@ark-ui/svelte/locale'
  import { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon, LoaderIcon } from 'lucide-svelte'
  import styles from 'styles/async-list.module.css'

  interface User {
    id: number
    name: string
    username: string
    email: string
    phone: string
    website: string
  }

  const collator = useCollator()

  const list = useAsyncList<User>({
    autoReload: true,
    load: async () => {
      const response = await fetch('https://jsonplaceholder.typicode.com/users?_limit=5')
      const data = await response.json()
      return { items: data }
    },
    sort({ items, descriptor }) {
      return {
        items: items.sort((a, b) => {
          const { column, direction } = descriptor
          let cmp = collator().compare(String(a[column]), String(b[column]))
          if (direction === 'descending') {
            cmp *= -1
          }
          return cmp
        }),
      }
    },
  })

  const handleSort = (column: keyof User) => {
    const currentSort = list().sortDescriptor
    let direction: 'ascending' | 'descending' = 'ascending'

    if (currentSort?.column === column && currentSort.direction === 'ascending') {
      direction = 'descending'
    }

    list().sort({ column, direction })
  }

  const columns = [
    { key: 'name', label: 'Name' },
    { key: 'username', label: 'Username' },
    { key: 'email', label: 'Email' },
  ]

  const descriptor = $derived(list().sortDescriptor)
</script>

<div class={styles.Root}>
  {#if list().loading}
    <div class={styles.Loading}>
      <LoaderIcon class={styles.Spinner} /> Loading
    </div>
  {/if}
  {#if list().error}
    <div class={styles.Error}>Error: {list().error.message}</div>
  {/if}
  <div class={styles.Status}>Sorted by: {descriptor ? `${descriptor.column} (${descriptor.direction})` : 'none'}</div>

  <table class={styles.Table}>
    <thead>
      <tr>
        {#each columns as { key, label }}
          <th onclick={() => handleSort(key as keyof User)}>
            {label}
            {#if list().sortDescriptor?.column === key}
              {#if list().sortDescriptor?.direction === 'ascending'}
                <ArrowUpIcon />
              {:else}
                <ArrowDownIcon />
              {/if}
            {:else}
              <ArrowUpDownIcon />
            {/if}
          </th>
        {/each}
      </tr>
    </thead>
    <tbody>
      {#each list().items as user}
        <tr>
          <td>{user.name}</td>
          <td>{user.username}</td>
          <td>{user.email}</td>
        </tr>
      {/each}
    </tbody>
  </table>
</div>

```

### Sorting (Server-side)

Send sort parameters to the server and reload data when sorting changes.

**Example: async-list/sort-server-side**

#### React

```tsx
import { useAsyncList } from '@ark-ui/react/collection'
import { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon, LoaderIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/async-list.module.css'

interface Product {
  id: number
  title: string
  price: number
  description: string
  category: string
  image: string
  rating: {
    rate: number
    count: number
  }
}

export const SortServerSide = () => {
  const list = useAsyncList<Product>({
    autoReload: true,
    async load({ sortDescriptor }) {
      const url = new URL('https://fakestoreapi.com/products')
      url.searchParams.set('limit', '5')
      if (sortDescriptor) {
        const { direction } = sortDescriptor
        url.searchParams.set('sort', direction === 'ascending' ? 'asc' : 'desc')
      }
      const response = await fetch(url)
      const items = await response.json()
      return { items }
    },
  })

  const handleSort = (column: keyof Product) => {
    const currentSort = list.sortDescriptor
    let direction: 'ascending' | 'descending' = 'ascending'
    if (currentSort?.column === column && currentSort.direction === 'ascending') {
      direction = 'descending'
    }
    list.sort({ column, direction })
  }

  const getSortIcon = (column: keyof Product) => {
    const desc = list.sortDescriptor
    if (desc?.column !== column) return <ArrowUpDownIcon />
    return desc.direction === 'ascending' ? <ArrowUpIcon /> : <ArrowDownIcon />
  }

  return (
    <div className={styles.Root}>
      <div className={styles.Header}>
        <button className={button.Root} onClick={() => handleSort('title')}>
          Sort by title {getSortIcon('title')}
        </button>
        {list.loading && (
          <span className={styles.Loading}>
            <LoaderIcon className={styles.Spinner} /> Loading
          </span>
        )}
      </div>

      {list.error && <div className={styles.Error}>Error: {list.error.message}</div>}

      <div className={styles.ItemGroup}>
        {list.items.map((product) => (
          <div key={product.id} className={styles.Item} data-variant="outline">
            <div className={styles.ItemMedia}>
              <img src={product.image} alt={product.title} />
            </div>
            <div className={styles.ItemContent}>
              <div className={styles.ItemTitle}>{product.title}</div>
              <div className={styles.ItemDescription}>${product.price}</div>
              <div className={styles.ItemMeta}>
                {product.category} • {product.rating.rate} ({product.rating.count} reviews)
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  )
}

```

#### Solid

```tsx
import { useAsyncList } from '@ark-ui/solid/collection'
import { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon, LoaderIcon } from 'lucide-solid'
import { For } from 'solid-js'
import button from 'styles/button.module.css'
import styles from 'styles/async-list.module.css'

interface Product {
  id: number
  title: string
  price: number
  description: string
  category: string
  image: string
  rating: {
    rate: number
    count: number
  }
}

export const SortServerSide = () => {
  const list = useAsyncList<Product>({
    autoReload: true,
    async load({ sortDescriptor }) {
      const url = new URL('https://fakestoreapi.com/products')
      url.searchParams.set('limit', '5')
      if (sortDescriptor) {
        const { direction } = sortDescriptor
        url.searchParams.set('sort', direction === 'ascending' ? 'asc' : 'desc')
      }
      const response = await fetch(url)
      const items = await response.json()
      return { items }
    },
  })

  const handleSort = (column: keyof Product) => {
    const currentSort = list().sortDescriptor
    let direction: 'ascending' | 'descending' = 'ascending'
    if (currentSort?.column === column && currentSort.direction === 'ascending') {
      direction = 'descending'
    }
    list().sort({ column, direction })
  }

  const getSortIcon = (column: keyof Product) => {
    const desc = list().sortDescriptor
    if (desc?.column !== column) return <ArrowUpDownIcon />
    return desc.direction === 'ascending' ? <ArrowUpIcon /> : <ArrowDownIcon />
  }

  return (
    <div class={styles.Root}>
      <div class={styles.Header}>
        <button class={button.Root} onClick={() => handleSort('title')}>
          Sort by title {getSortIcon('title')}
        </button>
        {list().loading && (
          <span class={styles.Loading}>
            <LoaderIcon class={styles.Spinner} /> Loading
          </span>
        )}
      </div>

      {list().error && <div class={styles.Error}>Error: {list().error.message}</div>}

      <div class={styles.ItemGroup}>
        <For each={list().items}>
          {(product) => (
            <div class={styles.Item} data-variant="outline">
              <div class={styles.ItemMedia}>
                <img src={product.image} alt={product.title} />
              </div>
              <div class={styles.ItemContent}>
                <div class={styles.ItemTitle}>{product.title}</div>
                <div class={styles.ItemDescription}>${product.price}</div>
                <div class={styles.ItemMeta}>
                  {product.category} • {product.rating.rate} ({product.rating.count} reviews)
                </div>
              </div>
            </div>
          )}
        </For>
      </div>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { useAsyncList } from '@ark-ui/vue/collection'
import { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon, LoaderIcon } from 'lucide-vue-next'
import button from 'styles/button.module.css'
import styles from 'styles/async-list.module.css'

interface Product {
  id: number
  title: string
  price: number
  description: string
  category: string
  image: string
  rating: {
    rate: number
    count: number
  }
}

const list = useAsyncList<Product>({
  autoReload: true,
  async load({ sortDescriptor }) {
    const url = new URL('https://fakestoreapi.com/products')
    url.searchParams.set('limit', '5')
    if (sortDescriptor) {
      const { direction } = sortDescriptor
      url.searchParams.set('sort', direction === 'ascending' ? 'asc' : 'desc')
    }
    const response = await fetch(url)
    const items = await response.json()
    return { items }
  },
})

const handleSort = (column: keyof Product) => {
  const currentSort = list.value.sortDescriptor
  let direction: 'ascending' | 'descending' = 'ascending'
  if (currentSort?.column === column && currentSort.direction === 'ascending') {
    direction = 'descending'
  }
  list.value.sort({ column, direction })
}
</script>

<template>
  <div :class="styles.Root">
    <div :class="styles.Header">
      <button :class="button.Root" @click="handleSort('title')">
        Sort by title
        <template v-if="list.sortDescriptor?.column === 'title'">
          <ArrowUpIcon v-if="list.sortDescriptor?.direction === 'ascending'" />
          <ArrowDownIcon v-else />
        </template>
        <ArrowUpDownIcon v-else />
      </button>
      <span v-if="list.loading" :class="styles.Loading">
        <LoaderIcon :class="styles.Spinner" />
        Loading
      </span>
    </div>

    <div v-if="list.error" :class="styles.Error">Error: {{ list.error.message }}</div>

    <div :class="styles.ItemGroup">
      <div v-for="product in list.items" :key="product.id" :class="styles.Item" data-variant="outline">
        <div :class="styles.ItemMedia">
          <img :src="product.image" :alt="product.title" />
        </div>
        <div :class="styles.ItemContent">
          <div :class="styles.ItemTitle">{{ product.title }}</div>
          <div :class="styles.ItemDescription">${{ product.price }}</div>
          <div :class="styles.ItemMeta">
            {{ product.category }} • {{ product.rating.rate }} ({{ product.rating.count }} reviews)
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { useAsyncList } from '@ark-ui/svelte/collection'
  import { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon, LoaderIcon } from 'lucide-svelte'
  import button from 'styles/button.module.css'
  import styles from 'styles/async-list.module.css'

  interface Product {
    id: number
    title: string
    price: number
    description: string
    category: string
    image: string
    rating: {
      rate: number
      count: number
    }
  }

  const list = useAsyncList<Product>({
    autoReload: true,
    async load({ sortDescriptor }) {
      const url = new URL('https://fakestoreapi.com/products')
      url.searchParams.set('limit', '5')
      if (sortDescriptor) {
        const { direction } = sortDescriptor
        url.searchParams.set('sort', direction === 'ascending' ? 'asc' : 'desc')
      }
      const response = await fetch(url)
      const items = await response.json()
      return { items }
    },
  })

  const handleSort = (column: keyof Product) => {
    const currentSort = list().sortDescriptor
    let direction: 'ascending' | 'descending' = 'ascending'
    if (currentSort?.column === column && currentSort.direction === 'ascending') {
      direction = 'descending'
    }
    list().sort({ column, direction })
  }
</script>

<div class={styles.Root}>
  <div class={styles.Header}>
    <button class={button.Root} onclick={() => handleSort('title')}>
      Sort by title
      {#if list().sortDescriptor?.column === 'title'}
        {#if list().sortDescriptor?.direction === 'ascending'}
          <ArrowUpIcon />
        {:else}
          <ArrowDownIcon />
        {/if}
      {:else}
        <ArrowUpDownIcon />
      {/if}
    </button>
    {#if list().loading}
      <span class={styles.Loading}>
        <LoaderIcon class={styles.Spinner} /> Loading
      </span>
    {/if}
  </div>

  {#if list().error}
    <div class={styles.Error}>Error: {list().error.message}</div>
  {/if}

  <div class={styles.ItemGroup}>
    {#each list().items as product}
      <div class={styles.Item} data-variant="outline">
        <div class={styles.ItemMedia}>
          <img src={product.image} alt={product.title} />
        </div>
        <div class={styles.ItemContent}>
          <div class={styles.ItemTitle}>{product.title}</div>
          <div class={styles.ItemDescription}>${product.price}</div>
          <div class={styles.ItemMeta}>
            {product.category} • {product.rating.rate} ({product.rating.count} reviews)
          </div>
        </div>
      </div>
    {/each}
  </div>
</div>

```

### Dependencies

Automatically reload data when dependencies change, such as filter selections or external state.

**Example: async-list/dependencies**

#### React

```tsx
import { useAsyncList } from '@ark-ui/react/collection'
import { LoaderIcon } from 'lucide-react'
import { useState } from 'react'
import field from 'styles/field.module.css'
import styles from 'styles/async-list.module.css'

const LIMIT = 5

interface User {
  id: number
  name: string
  email: string
  department: string
  role: string
}

export const Dependencies = () => {
  const [selectedDepartment, setSelectedDepartment] = useState<string>('')
  const [selectedRole, setSelectedRole] = useState<string>('')

  const list = useAsyncList<User>({
    initialItems: mockUsers.slice(0, LIMIT),
    dependencies: [selectedDepartment, selectedRole],
    async load({ filterText }) {
      await delay(400)

      let items = mockUsers

      if (selectedDepartment) {
        items = items.filter((user) => user.department === selectedDepartment)
      }

      if (selectedRole) {
        items = items.filter((user) => user.role === selectedRole)
      }

      if (filterText) {
        items = items.filter(
          (user) =>
            user.name.toLowerCase().includes(filterText.toLowerCase()) ||
            user.email.toLowerCase().includes(filterText.toLowerCase()),
        )
      }

      return { items: items.slice(0, LIMIT) }
    },
  })

  return (
    <div className={styles.Root}>
      <div className={styles.Header}>
        <select
          className={field.Select}
          value={selectedDepartment}
          onChange={(e) => setSelectedDepartment(e.target.value)}
        >
          <option value="">All Departments</option>
          {departments.map((dept) => (
            <option key={dept} value={dept}>
              {dept}
            </option>
          ))}
        </select>

        <select className={field.Select} value={selectedRole} onChange={(e) => setSelectedRole(e.target.value)}>
          <option value="">All Roles</option>
          {roles.map((role) => (
            <option key={role} value={role}>
              {role}
            </option>
          ))}
        </select>

        <input
          className={field.Input}
          type="text"
          placeholder="Search..."
          value={list.filterText}
          onChange={(e) => list.setFilterText(e.target.value)}
        />

        {list.loading && (
          <span className={styles.Loading}>
            <LoaderIcon className={styles.Spinner} /> Loading
          </span>
        )}
      </div>

      {list.error && <div className={styles.Error}>Error: {list.error.message}</div>}

      <div className={styles.Status}>Found {list.items.length} users</div>

      <div className={styles.ItemGroup}>
        {list.items.map((user) => (
          <div key={user.id} className={styles.Item}>
            <div className={styles.ItemContent}>
              <div className={styles.ItemTitle}>{user.name}</div>
              <div className={styles.ItemDescription}>{user.email}</div>
              <div className={styles.ItemMeta}>
                {user.department} • {user.role}
              </div>
            </div>
          </div>
        ))}
      </div>

      {list.items.length === 0 && !list.loading && (
        <div className={styles.Empty}>No users found with current filters</div>
      )}
    </div>
  )
}

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

const departments = ['Engineering', 'Marketing', 'Sales', 'Support']

const roles = [
  'Senior Developer',
  'Marketing Manager',
  'Frontend Developer',
  'Sales Representative',
  'DevOps Engineer',
  'Customer Success',
  'Content Creator',
  'Backend Developer',
  'Account Manager',
  'Technical Support',
  'Brand Manager',
  'Full Stack Developer',
  'Sales Director',
  'Support Manager',
  'UI Designer',
  'Digital Marketer',
  'Mobile Developer',
  'Business Development',
  'Documentation Specialist',
  'Social Media Manager',
]

const mockUsers: User[] = [
  { id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
  { id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
  { id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
  { id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
  { id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
  { id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
  { id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
  { id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
  { id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
  { id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
  { id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
  { id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
  { id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
  { id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
  { id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
  { id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
  { id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
  { id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
  { id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
  { id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
]

```

#### Solid

```tsx
import { useAsyncList } from '@ark-ui/solid/collection'
import { LoaderIcon } from 'lucide-solid'
import { createSignal, For } from 'solid-js'
import field from 'styles/field.module.css'
import styles from 'styles/async-list.module.css'

const LIMIT = 5

interface User {
  id: number
  name: string
  email: string
  department: string
  role: string
}

export const Dependencies = () => {
  const [selectedDepartment, setSelectedDepartment] = createSignal<string>('')
  const [selectedRole, setSelectedRole] = createSignal<string>('')

  const list = useAsyncList<User>({
    initialItems: mockUsers.slice(0, LIMIT),
    get dependencies() {
      return [selectedDepartment(), selectedRole()]
    },
    async load({ filterText }: { filterText?: string }) {
      await delay(400)

      let items = mockUsers

      if (selectedDepartment()) {
        items = items.filter((user) => user.department === selectedDepartment())
      }

      if (selectedRole()) {
        items = items.filter((user) => user.role === selectedRole())
      }

      if (filterText) {
        items = items.filter(
          (user) =>
            user.name.toLowerCase().includes(filterText.toLowerCase()) ||
            user.email.toLowerCase().includes(filterText.toLowerCase()),
        )
      }

      return { items: items.slice(0, LIMIT) }
    },
  })

  return (
    <div class={styles.Root}>
      <div class={styles.Header}>
        <select
          class={field.Select}
          value={selectedDepartment()}
          onChange={(e) => setSelectedDepartment(e.target.value)}
        >
          <option value="">All Departments</option>
          <For each={departments}>{(dept) => <option value={dept}>{dept}</option>}</For>
        </select>

        <select class={field.Select} value={selectedRole()} onChange={(e) => setSelectedRole(e.target.value)}>
          <option value="">All Roles</option>
          <For each={roles}>{(role) => <option value={role}>{role}</option>}</For>
        </select>

        <input
          class={field.Input}
          type="text"
          placeholder="Search..."
          value={list().filterText}
          onInput={(e) => list().setFilterText(e.target.value)}
        />

        {list().loading && (
          <span class={styles.Loading}>
            <LoaderIcon class={styles.Spinner} /> Loading
          </span>
        )}
      </div>

      {list().error && <div class={styles.Error}>Error: {list().error.message}</div>}

      <div class={styles.Status}>Found {list().items.length} users</div>

      <div class={styles.ItemGroup}>
        <For each={list().items}>
          {(user) => (
            <div class={styles.Item}>
              <div class={styles.ItemContent}>
                <div class={styles.ItemTitle}>{user.name}</div>
                <div class={styles.ItemDescription}>{user.email}</div>
                <div class={styles.ItemMeta}>
                  {user.department} • {user.role}
                </div>
              </div>
            </div>
          )}
        </For>
      </div>

      {list().items.length === 0 && !list().loading && (
        <div class={styles.Empty}>No users found with current filters</div>
      )}
    </div>
  )
}

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

const departments = ['Engineering', 'Marketing', 'Sales', 'Support']

const roles = [
  'Senior Developer',
  'Marketing Manager',
  'Frontend Developer',
  'Sales Representative',
  'DevOps Engineer',
  'Customer Success',
  'Content Creator',
  'Backend Developer',
  'Account Manager',
  'Technical Support',
  'Brand Manager',
  'Full Stack Developer',
  'Sales Director',
  'Support Manager',
  'UI Designer',
  'Digital Marketer',
  'Mobile Developer',
  'Business Development',
  'Documentation Specialist',
  'Social Media Manager',
]

const mockUsers: User[] = [
  { id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
  { id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
  { id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
  { id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
  { id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
  { id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
  { id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
  { id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
  { id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
  { id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
  { id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
  { id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
  { id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
  { id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
  { id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
  { id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
  { id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
  { id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
  { id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
  { id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
]

```

#### Vue

```vue
<script setup lang="ts">
import { useAsyncList } from '@ark-ui/vue/collection'
import { LoaderIcon } from 'lucide-vue-next'
import { ref } from 'vue'
import field from 'styles/field.module.css'
import styles from 'styles/async-list.module.css'

const LIMIT = 5

interface User {
  id: number
  name: string
  email: string
  department: string
  role: string
}

const selectedDepartment = ref('')
const selectedRole = ref('')

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

const departments = ['Engineering', 'Marketing', 'Sales', 'Support']

const roles = [
  'Senior Developer',
  'Marketing Manager',
  'Frontend Developer',
  'Sales Representative',
  'DevOps Engineer',
  'Customer Success',
  'Content Creator',
  'Backend Developer',
  'Account Manager',
  'Technical Support',
  'Brand Manager',
  'Full Stack Developer',
  'Sales Director',
  'Support Manager',
  'UI Designer',
  'Digital Marketer',
  'Mobile Developer',
  'Business Development',
  'Documentation Specialist',
  'Social Media Manager',
]

const mockUsers: User[] = [
  { id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
  { id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
  { id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
  { id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
  { id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
  { id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
  { id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
  { id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
  { id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
  { id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
  { id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
  { id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
  { id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
  { id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
  { id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
  { id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
  { id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
  { id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
  { id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
  { id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
]

const list = useAsyncList<User>({
  initialItems: mockUsers.slice(0, LIMIT),
  get dependencies() {
    return [selectedDepartment.value, selectedRole.value]
  },
  async load({ filterText }: { filterText?: string } = {}) {
    await delay(400)

    let items = mockUsers

    if (selectedDepartment.value) {
      items = items.filter((user) => user.department === selectedDepartment.value)
    }

    if (selectedRole.value) {
      items = items.filter((user) => user.role === selectedRole.value)
    }

    if (filterText) {
      items = items.filter(
        (user) =>
          user.name.toLowerCase().includes(filterText.toLowerCase()) ||
          user.email.toLowerCase().includes(filterText.toLowerCase()),
      )
    }

    return { items: items.slice(0, LIMIT) }
  },
})
</script>

<template>
  <div :class="styles.Root">
    <div :class="styles.Header">
      <select :class="field.Select" v-model="selectedDepartment">
        <option value="">All Departments</option>
        <option v-for="dept in departments" :key="dept" :value="dept">
          {{ dept }}
        </option>
      </select>

      <select :class="field.Select" v-model="selectedRole">
        <option value="">All Roles</option>
        <option v-for="role in roles" :key="role" :value="role">
          {{ role }}
        </option>
      </select>

      <input
        :class="field.Input"
        type="text"
        placeholder="Search..."
        :value="list.filterText"
        @input="list.setFilterText(($event.currentTarget as HTMLInputElement).value ?? '')"
      />

      <span v-if="list.loading" :class="styles.Loading">
        <LoaderIcon :class="styles.Spinner" />
        Loading
      </span>
    </div>

    <div v-if="list.error" :class="styles.Error">Error: {{ list.error.message }}</div>

    <div :class="styles.Status">Found {{ list.items.length }} users</div>

    <div :class="styles.ItemGroup">
      <div v-for="user in list.items" :key="user.id" :class="styles.Item">
        <div :class="styles.ItemContent">
          <div :class="styles.ItemTitle">{{ user.name }}</div>
          <div :class="styles.ItemDescription">{{ user.email }}</div>
          <div :class="styles.ItemMeta">{{ user.department }} • {{ user.role }}</div>
        </div>
      </div>
    </div>

    <div v-if="list.items.length === 0 && !list.loading" :class="styles.Empty">No users found with current filters</div>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { useAsyncList } from '@ark-ui/svelte/collection'
  import { LoaderIcon } from 'lucide-svelte'
  import field from 'styles/field.module.css'
  import styles from 'styles/async-list.module.css'

  const LIMIT = 5

  interface User {
    id: number
    name: string
    email: string
    department: string
    role: string
  }

  const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

  const departments = ['Engineering', 'Marketing', 'Sales', 'Support']

  const roles = [
    'Senior Developer',
    'Marketing Manager',
    'Frontend Developer',
    'Sales Representative',
    'DevOps Engineer',
    'Customer Success',
    'Content Creator',
    'Backend Developer',
    'Account Manager',
    'Technical Support',
    'Brand Manager',
    'Full Stack Developer',
    'Sales Director',
    'Support Manager',
    'UI Designer',
    'Digital Marketer',
    'Mobile Developer',
    'Business Development',
    'Documentation Specialist',
    'Social Media Manager',
  ]

  const mockUsers: User[] = [
    { id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', role: 'Senior Developer' },
    { id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', role: 'Marketing Manager' },
    { id: 3, name: 'Carol Davis', email: 'carol@example.com', department: 'Engineering', role: 'Frontend Developer' },
    { id: 4, name: 'David Wilson', email: 'david@example.com', department: 'Sales', role: 'Sales Representative' },
    { id: 5, name: 'Eva Brown', email: 'eva@example.com', department: 'Engineering', role: 'DevOps Engineer' },
    { id: 6, name: 'Frank Miller', email: 'frank@example.com', department: 'Support', role: 'Customer Success' },
    { id: 7, name: 'Grace Lee', email: 'grace@example.com', department: 'Marketing', role: 'Content Creator' },
    { id: 8, name: 'Henry Taylor', email: 'henry@example.com', department: 'Engineering', role: 'Backend Developer' },
    { id: 9, name: 'Ivy Anderson', email: 'ivy@example.com', department: 'Sales', role: 'Account Manager' },
    { id: 10, name: 'Jack Thompson', email: 'jack@example.com', department: 'Support', role: 'Technical Support' },
    { id: 11, name: 'Kate Martinez', email: 'kate@example.com', department: 'Marketing', role: 'Brand Manager' },
    { id: 12, name: 'Liam Garcia', email: 'liam@example.com', department: 'Engineering', role: 'Full Stack Developer' },
    { id: 13, name: 'Mia Rodriguez', email: 'mia@example.com', department: 'Sales', role: 'Sales Director' },
    { id: 14, name: 'Noah Lopez', email: 'noah@example.com', department: 'Support', role: 'Support Manager' },
    { id: 15, name: 'Olivia White', email: 'olivia@example.com', department: 'Engineering', role: 'UI Designer' },
    { id: 16, name: 'Paul Harris', email: 'paul@example.com', department: 'Marketing', role: 'Digital Marketer' },
    { id: 17, name: 'Quinn Clark', email: 'quinn@example.com', department: 'Engineering', role: 'Mobile Developer' },
    { id: 18, name: 'Ruby Lewis', email: 'ruby@example.com', department: 'Sales', role: 'Business Development' },
    { id: 19, name: 'Sam Young', email: 'sam@example.com', department: 'Support', role: 'Documentation Specialist' },
    { id: 20, name: 'Tina Walker', email: 'tina@example.com', department: 'Marketing', role: 'Social Media Manager' },
  ]

  let selectedDepartment = $state('')
  let selectedRole = $state('')

  const list = useAsyncList<User>({
    initialItems: mockUsers.slice(0, LIMIT),
    get dependencies() {
      return [selectedDepartment, selectedRole]
    },
    async load({ filterText }: { filterText?: string } = {}) {
      await delay(400)

      let items = mockUsers

      if (selectedDepartment) {
        items = items.filter((user) => user.department === selectedDepartment)
      }

      if (selectedRole) {
        items = items.filter((user) => user.role === selectedRole)
      }

      if (filterText) {
        items = items.filter(
          (user) =>
            user.name.toLowerCase().includes(filterText.toLowerCase()) ||
            user.email.toLowerCase().includes(filterText.toLowerCase()),
        )
      }

      return { items: items.slice(0, LIMIT) }
    },
  })
</script>

<div class={styles.Root}>
  <div class={styles.Header}>
    <select class={field.Select} bind:value={selectedDepartment}>
      <option value="">All Departments</option>
      {#each departments as dept}
        <option value={dept}>{dept}</option>
      {/each}
    </select>

    <select class={field.Select} bind:value={selectedRole}>
      <option value="">All Roles</option>
      {#each roles as role}
        <option value={role}>{role}</option>
      {/each}
    </select>

    <input
      class={field.Input}
      type="text"
      placeholder="Search..."
      value={list().filterText}
      oninput={(e) => list().setFilterText(e.currentTarget.value)}
    />

    {#if list().loading}
      <span class={styles.Loading}>
        <LoaderIcon class={styles.Spinner} /> Loading
      </span>
    {/if}
  </div>

  {#if list().error}
    <div class={styles.Error}>Error: {list().error.message}</div>
  {/if}

  <div class={styles.Status}>Found {list().items.length} users</div>

  <div class={styles.ItemGroup}>
    {#each list().items as user}
      <div class={styles.Item}>
        <div class={styles.ItemContent}>
          <div class={styles.ItemTitle}>{user.name}</div>
          <div class={styles.ItemDescription}>{user.email}</div>
          <div class={styles.ItemMeta}>
            {user.department} • {user.role}
          </div>
        </div>
      </div>
    {/each}
  </div>

  {#if list().items.length === 0 && !list().loading}
    <div class={styles.Empty}>No users found with current filters</div>
  {/if}
</div>

```

## API Reference

### Props

- **load** (`(params: LoadParams<C>) => Promise<LoadResult<T, C>>`) - Function to load data asynchronously
- **sort** (`(params: SortParams<T>) => Promise<SortResult<T>> | SortResult<T>`) - Optional function for client-side
  sorting
- **autoReload** (`boolean`, default: `false`) - Whether to automatically reload data on mount
- **initialItems** (`T[]`, default: `[]`) - Initial items to display before first load
- **dependencies** (`any[]`, default: `[]`) - Values that trigger a reload when changed
- **initialFilterText** (`string`, default: `''`) - Initial filter text value
- **initialSortDescriptor** (`SortDescriptor | null`) - Initial sort configuration

### Load Parameters

The `load` function receives an object with the following properties:

- **cursor** (`C | undefined`) - Current cursor for pagination
- **filterText** (`string`) - Current filter text
- **sortDescriptor** (`SortDescriptor | null`) - Current sort configuration
- **signal** (`AbortSignal`) - AbortController signal for request cancellation

### Load Result

The `load` function should return an object with:

- **items** (`T[]`) - The loaded items
- **cursor** (`C | undefined`) - Optional cursor for next page

### Sort Parameters

The `sort` function receives an object with:

- **items** (`T[]`) - Current items to sort
- **descriptor** (`SortDescriptor`) - Sort configuration with `column` and `direction`

### Return Value

The hook returns an object with the following properties and methods:

#### State Properties

- **items** (`T[]`) - Current list of items
- **loading** (`boolean`) - Whether a load operation is in progress
- **error** (`Error | null`) - Any error from the last operation
- **cursor** (`C | undefined`) - Current cursor for pagination
- **filterText** (`string`) - Current filter text
- **sortDescriptor** (`SortDescriptor | null`) - Current sort configuration

#### Methods

- **reload** (`() => void`) - Reload data from the beginning
- **loadMore** (`() => void`) - Load more items (when cursor is available)
- **setFilterText** (`(text: string) => void`) - Set filter text and reload
- **sort** (`(descriptor: SortDescriptor) => void`) - Apply sorting

#### Types

```tsx
interface SortDescriptor {
  column: string
  direction: 'ascending' | 'descending'
}

interface LoadParams<C> {
  cursor?: C
  filterText: string
  sortDescriptor?: SortDescriptor | null
  signal: AbortSignal
}

interface LoadResult<T, C> {
  items: T[]
  cursor?: C
}
```