# Combobox

URL: https://ark-ui.com/docs/components/combobox
LLM: https://ark-ui.com/llms.txt/components/combobox

A single input field that combines the functionality of a select and input.

---



## Anatomy



```tsx
<Combobox.Root>
  <Combobox.Label />
  <Combobox.Control>
    <Combobox.Input />
    <Combobox.Trigger />
    <Combobox.ClearTrigger />
  </Combobox.Control>
  <Combobox.Positioner>
    <Combobox.Content>
      <Combobox.ItemGroup>
        <Combobox.ItemGroupLabel />
        <Combobox.Item>
          <Combobox.ItemText />
          <Combobox.ItemIndicator />
        </Combobox.Item>
      </Combobox.ItemGroup>
    </Combobox.Content>
  </Combobox.Positioner>
</Combobox.Root>
```

## Examples

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
import { CheckIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react'
import styles from 'styles/combobox.module.css'

export const Basic = () => {
  const { contains } = useFilter({ sensitivity: 'base' })

  const { collection, filter } = useListCollection({
    initialItems: [
      { label: 'Apple', value: 'apple' },
      { label: 'Banana', value: 'banana' },
      { label: 'Orange', value: 'orange' },
      { label: 'Mango', value: 'mango' },
      { label: 'Pineapple', value: 'pineapple' },
      { label: 'Strawberry', value: 'strawberry' },
    ],
    filter: contains,
  })

  const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
    filter(details.inputValue)
  }

  return (
    <Combobox.Root className={styles.Root} collection={collection} onInputValueChange={handleInputChange}>
      <Combobox.Label className={styles.Label}>Favorite Fruit</Combobox.Label>
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. Apple" />
        <div className={styles.Indicators}>
          <Combobox.ClearTrigger className={styles.ClearTrigger}>
            <XIcon />
          </Combobox.ClearTrigger>
          <Combobox.Trigger className={styles.Trigger}>
            <ChevronsUpDownIcon />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            {collection.items.map((item) => (
              <Combobox.Item className={styles.Item} key={item.value} item={item}>
                <Combobox.ItemText className={styles.ItemText}>{item.label}</Combobox.ItemText>
                <Combobox.ItemIndicator className={styles.ItemIndicator}>
                  <CheckIcon />
                </Combobox.ItemIndicator>
              </Combobox.Item>
            ))}
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.Root>
  )
}
```

### Auto Highlight

Automatically highlight the first matching item as the user types by setting `inputBehavior="autohighlight"`.

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
import { CheckIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react'
import styles from 'styles/combobox.module.css'

export const AutoHighlight = () => {
  const { contains } = useFilter({ sensitivity: 'base' })

  const { collection, filter } = useListCollection({
    initialItems: [
      { label: 'Engineering', value: 'engineering' },
      { label: 'Marketing', value: 'marketing' },
      { label: 'Sales', value: 'sales' },
      { label: 'Finance', value: 'finance' },
      { label: 'Human Resources', value: 'hr' },
      { label: 'Operations', value: 'operations' },
      { label: 'Product', value: 'product' },
      { label: 'Customer Success', value: 'customer-success' },
      { label: 'Legal', value: 'legal' },
      { label: 'Information Technology', value: 'information-technology' },
      { label: 'Design', value: 'design' },
    ],
    filter: contains,
  })

  const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
    filter(details.inputValue)
  }

  return (
    <Combobox.Root
      className={styles.Root}
      collection={collection}
      onInputValueChange={handleInputChange}
      inputBehavior="autohighlight"
    >
      <Combobox.Label className={styles.Label}>Department</Combobox.Label>
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. Engineering" />
        <div className={styles.Indicators}>
          <Combobox.ClearTrigger className={styles.ClearTrigger}>
            <XIcon />
          </Combobox.ClearTrigger>
          <Combobox.Trigger className={styles.Trigger}>
            <ChevronsUpDownIcon />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            <Combobox.Empty className={styles.Item}>No results found</Combobox.Empty>
            {collection.items.map((item) => (
              <Combobox.Item className={styles.Item} key={item.value} item={item}>
                <Combobox.ItemText className={styles.ItemText}>{item.label}</Combobox.ItemText>
                <Combobox.ItemIndicator className={styles.ItemIndicator}>
                  <CheckIcon />
                </Combobox.ItemIndicator>
              </Combobox.Item>
            ))}
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.Root>
  )
}
```

### Inline Autocomplete

Complete the input value with the first matching item by setting `inputBehavior="autocomplete"`. Use with `startsWith`
filter for best results.

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
import { CheckIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react'
import styles from 'styles/combobox.module.css'

export const InlineAutocomplete = () => {
  const { startsWith } = useFilter({ sensitivity: 'base' })

  const { collection, filter } = useListCollection({
    initialItems: [
      { label: 'Whale', value: 'whale' },
      { label: 'Dolphin', value: 'dolphin' },
      { label: 'Shark', value: 'shark' },
      { label: 'Octopus', value: 'octopus' },
      { label: 'Jellyfish', value: 'jellyfish' },
      { label: 'Seahorse', value: 'seahorse' },
    ],
    filter: startsWith,
  })

  const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
    filter(details.inputValue)
  }

  return (
    <Combobox.Root
      className={styles.Root}
      collection={collection}
      onInputValueChange={handleInputChange}
      inputBehavior="autocomplete"
    >
      <Combobox.Label className={styles.Label}>Sea Creature</Combobox.Label>
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. Dolphin" />
        <div className={styles.Indicators}>
          <Combobox.ClearTrigger className={styles.ClearTrigger}>
            <XIcon />
          </Combobox.ClearTrigger>
          <Combobox.Trigger className={styles.Trigger}>
            <ChevronsUpDownIcon />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            <Combobox.Empty className={styles.Item}>No results found</Combobox.Empty>
            {collection.items.map((item) => (
              <Combobox.Item className={styles.Item} key={item.value} item={item}>
                <Combobox.ItemText className={styles.ItemText}>{item.label}</Combobox.ItemText>
                <Combobox.ItemIndicator className={styles.ItemIndicator}>
                  <CheckIcon />
                </Combobox.ItemIndicator>
              </Combobox.Item>
            ))}
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.Root>
  )
}
```

### Grouping

To group related combobox items, use the `groupBy` prop on the collection and `collection.group()` to iterate the
groups.

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
import { CheckIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react'
import styles from 'styles/combobox.module.css'

export const Grouping = () => {
  const { contains } = useFilter({ sensitivity: 'base' })

  const { collection, filter } = useListCollection({
    initialItems,
    filter: contains,
    groupBy: (item) => item.continent,
  })

  const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
    filter(details.inputValue)
  }

  return (
    <Combobox.Root className={styles.Root} collection={collection} onInputValueChange={handleInputChange}>
      <Combobox.Label className={styles.Label}>Country</Combobox.Label>
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. Canada" />
        <div className={styles.Indicators}>
          <Combobox.ClearTrigger className={styles.ClearTrigger}>
            <XIcon />
          </Combobox.ClearTrigger>
          <Combobox.Trigger className={styles.Trigger}>
            <ChevronsUpDownIcon />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            {collection.group().map(([continent, group]) => (
              <Combobox.ItemGroup className={styles.ItemGroup} key={continent}>
                <Combobox.ItemGroupLabel className={styles.ItemGroupLabel}>{continent}</Combobox.ItemGroupLabel>
                {group.map((item) => (
                  <Combobox.Item className={styles.Item} key={item.value} item={item}>
                    <Combobox.ItemText className={styles.ItemText}>{item.label}</Combobox.ItemText>
                    <Combobox.ItemIndicator className={styles.ItemIndicator}>
                      <CheckIcon />
                    </Combobox.ItemIndicator>
                  </Combobox.Item>
                ))}
              </Combobox.ItemGroup>
            ))}
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.Root>
  )
}

const initialItems = [
  { label: 'Canada', value: 'ca', continent: 'North America' },
  { label: 'United States', value: 'us', continent: 'North America' },
  { label: 'Mexico', value: 'mx', continent: 'North America' },
  { label: 'United Kingdom', value: 'uk', continent: 'Europe' },
  { label: 'Germany', value: 'de', continent: 'Europe' },
  { label: 'France', value: 'fr', continent: 'Europe' },
  { label: 'Japan', value: 'jp', continent: 'Asia' },
  { label: 'South Korea', value: 'kr', continent: 'Asia' },
  { label: 'China', value: 'cn', continent: 'Asia' },
]
```

### Field

The `Field` component helps manage form-related state and accessibility attributes of a combobox. It includes handling
ARIA labels, helper text, and error text to ensure proper accessibility.

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { Field } from '@ark-ui/react/field'
import { useFilter } from '@ark-ui/react/locale'
import { CheckIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react'
import styles from 'styles/combobox.module.css'
import field from 'styles/field.module.css'

export const WithField = () => {
  const { contains } = useFilter({ sensitivity: 'base' })

  const { collection, filter } = useListCollection({
    initialItems,
    filter: contains,
  })

  const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
    filter(details.inputValue)
  }

  return (
    <Field.Root className={field.Root}>
      <Combobox.Root className={styles.Root} collection={collection} onInputValueChange={handleInputChange}>
        <Combobox.Label className={styles.Label}>Department</Combobox.Label>
        <Combobox.Control className={styles.Control}>
          <Combobox.Input className={styles.Input} placeholder="e.g. Engineering" />
          <div className={styles.Indicators}>
            <Combobox.ClearTrigger className={styles.ClearTrigger}>
              <XIcon />
            </Combobox.ClearTrigger>
            <Combobox.Trigger className={styles.Trigger}>
              <ChevronsUpDownIcon />
            </Combobox.Trigger>
          </div>
        </Combobox.Control>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            {collection.items.map((item) => (
              <Combobox.Item className={styles.Item} key={item.value} item={item}>
                <Combobox.ItemText className={styles.ItemText}>{item.label}</Combobox.ItemText>
                <Combobox.ItemIndicator className={styles.ItemIndicator}>
                  <CheckIcon />
                </Combobox.ItemIndicator>
              </Combobox.Item>
            ))}
          </Combobox.Content>
        </Combobox.Positioner>
      </Combobox.Root>
      <Field.HelperText className={field.HelperText}>Select your primary department</Field.HelperText>
      <Field.ErrorText className={field.ErrorText}>Department is required</Field.ErrorText>
    </Field.Root>
  )
}

const initialItems = [
  { label: 'Engineering', value: 'engineering' },
  { label: 'Design', value: 'design' },
  { label: 'Marketing', value: 'marketing' },
  { label: 'Sales', value: 'sales' },
  { label: 'Human Resources', value: 'hr' },
  { label: 'Finance', value: 'finance' },
]
```

### Context

Access the combobox's state with `Combobox.Context` or the `useComboboxContext` hook—useful for displaying the selected
value or building custom UI.

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
import { CheckIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react'
import styles from 'styles/combobox.module.css'

export const Context = () => {
  const { contains } = useFilter({ sensitivity: 'base' })

  const { collection, filter } = useListCollection({
    initialItems: [
      { label: 'Small', value: 'sm' },
      { label: 'Medium', value: 'md' },
      { label: 'Large', value: 'lg' },
      { label: 'Extra Large', value: 'xl' },
    ],
    filter: contains,
  })

  const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
    filter(details.inputValue)
  }

  return (
    <Combobox.Root className={styles.Root} collection={collection} onInputValueChange={handleInputChange}>
      <Combobox.Context>{(combobox) => <p>Selected: {combobox.valueAsString || 'None'}</p>}</Combobox.Context>
      <Combobox.Label className={styles.Label}>Size</Combobox.Label>
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. Medium" />
        <div className={styles.Indicators}>
          <Combobox.ClearTrigger className={styles.ClearTrigger}>
            <XIcon />
          </Combobox.ClearTrigger>
          <Combobox.Trigger className={styles.Trigger}>
            <ChevronsUpDownIcon />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            {collection.items.map((item) => (
              <Combobox.Item className={styles.Item} key={item.value} item={item}>
                <Combobox.ItemText className={styles.ItemText}>{item.label}</Combobox.ItemText>
                <Combobox.ItemIndicator className={styles.ItemIndicator}>
                  <CheckIcon />
                </Combobox.ItemIndicator>
              </Combobox.Item>
            ))}
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.Root>
  )
}
```

### Root Provider

An alternative way to control the combobox is to use the `RootProvider` component and the `useCombobox` hook. This way
you can access the state and methods from outside the component.

```tsx
import { Combobox, useCombobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
import { CheckIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/combobox.module.css'

const initialItems = [
  { label: 'Designer', value: 'designer' },
  { label: 'Developer', value: 'developer' },
  { label: 'Product Manager', value: 'pm' },
  { label: 'Data Scientist', value: 'data-scientist' },
  { label: 'DevOps Engineer', value: 'devops' },
  { label: 'Marketing Lead', value: 'marketing' },
]

export const RootProvider = () => {
  const { contains } = useFilter({ sensitivity: 'base' })

  const { collection, filter } = useListCollection({
    initialItems,
    filter: contains,
  })

  const combobox = useCombobox({
    collection,
    onInputValueChange(details) {
      filter(details.inputValue)
    },
  })

  return (
    <div className="stack">
      <button className={button.Root} onClick={() => combobox.focus()}>
        Focus
      </button>

      <Combobox.RootProvider className={styles.Root} value={combobox}>
        <Combobox.Label className={styles.Label}>Job Title</Combobox.Label>
        <Combobox.Control className={styles.Control}>
          <Combobox.Input className={styles.Input} placeholder="e.g. Designer" />
          <div className={styles.Indicators}>
            <Combobox.ClearTrigger className={styles.ClearTrigger}>
              <XIcon />
            </Combobox.ClearTrigger>
            <Combobox.Trigger className={styles.Trigger}>
              <ChevronsUpDownIcon />
            </Combobox.Trigger>
          </div>
        </Combobox.Control>
        <Portal>
          <Combobox.Positioner>
            <Combobox.Content className={styles.Content}>
              {collection.items.map((item) => (
                <Combobox.Item className={styles.Item} key={item.value} item={item}>
                  <Combobox.ItemText className={styles.ItemText}>{item.label}</Combobox.ItemText>
                  <Combobox.ItemIndicator className={styles.ItemIndicator}>
                    <CheckIcon />
                  </Combobox.ItemIndicator>
                </Combobox.Item>
              ))}
            </Combobox.Content>
          </Combobox.Positioner>
        </Portal>
      </Combobox.RootProvider>
    </div>
  )
}
```

### Links

Use the `asChild` prop to render the combobox items as links.

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react'
import styles from 'styles/combobox.module.css'

export const Links = () => {
  const { contains } = useFilter({ sensitivity: 'base' })

  const { collection, filter } = useListCollection({
    initialItems,
    filter: contains,
  })

  const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
    filter(details.inputValue)
  }

  return (
    <Combobox.Root
      className={styles.Root}
      collection={collection}
      onInputValueChange={handleInputChange}
      selectionBehavior="preserve"
    >
      <Combobox.Label className={styles.Label}>Developer Resources</Combobox.Label>
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. GitHub" />
        <div className={styles.Indicators}>
          <Combobox.Trigger className={styles.Trigger}>
            <ChevronsUpDownIcon />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            {collection.items.map((item) => (
              <Combobox.Item className={styles.Item} key={item.value} item={item} asChild>
                <a href={item.href}>
                  <Combobox.ItemText className={styles.ItemText}>{item.label}</Combobox.ItemText>
                  <Combobox.ItemIndicator className={styles.ItemIndicator}>
                    <CheckIcon />
                  </Combobox.ItemIndicator>
                </a>
              </Combobox.Item>
            ))}
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.Root>
  )
}

const initialItems = [
  { label: 'GitHub', href: 'https://github.com', value: 'github' },
  { label: 'Stack Overflow', href: 'https://stackoverflow.com', value: 'stackoverflow' },
  { label: 'MDN Web Docs', href: 'https://developer.mozilla.org', value: 'mdn' },
  { label: 'npm', href: 'https://www.npmjs.com', value: 'npm' },
  { label: 'TypeScript', href: 'https://www.typescriptlang.org', value: 'typescript' },
  { label: 'React', href: 'https://react.dev', value: 'react' },
]
```

### Rehydrate

When a combobox has a `defaultValue` or `value` but the `collection` is not loaded yet, you can rehydrate the value to
populate the input.

```tsx
import { Combobox, useCombobox, useComboboxContext, useListCollection } from '@ark-ui/react/combobox'
import { Portal } from '@ark-ui/react/portal'
import { CheckIcon } from 'lucide-react'
import { useRef, useState } from 'react'
import { useAsync } from 'react-use'
import styles from 'styles/combobox.module.css'

function ComboboxRehydrateValue() {
  const combobox = useComboboxContext()
  const hydrated = useRef(false)
  if (combobox.value.length && combobox.collection.size && !hydrated.current) {
    combobox.syncSelectedItems()
    hydrated.current = true
  }
  return null
}

export const RehydrateValue = () => {
  const [inputValue, setInputValue] = useState('')

  const { collection, set } = useListCollection<Character>({
    initialItems: [],
    itemToString: (item) => item.name,
    itemToValue: (item) => item.name,
  })

  const combobox = useCombobox({
    collection,
    defaultValue: ['C-3PO'],
    placeholder: 'Example: Dexter',
    inputValue,
    onInputValueChange: (e) => setInputValue(e.inputValue),
  })

  const state = useAsync(async () => {
    const response = await fetch(`https://swapi.py4e.com/api/people/?search=${inputValue}`)
    const data = await response.json()
    set(data.results)
  }, [inputValue, set])

  return (
    <Combobox.RootProvider className={styles.Root} value={combobox}>
      <Combobox.Label className={styles.Label}>Search Star Wars Characters</Combobox.Label>
      <ComboboxRehydrateValue />
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. Luke" />
      </Combobox.Control>

      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            {state.loading ? (
              <span style={{ padding: '0.5rem' }}>Loading...</span>
            ) : state.error ? (
              <span style={{ padding: '0.5rem' }}>{state.error.message}</span>
            ) : (
              collection.items.map((item) => (
                <Combobox.Item className={styles.Item} key={item.name} item={item}>
                  <Combobox.ItemText className={styles.ItemText}>
                    {item.name} - {item.height}cm / {item.mass}kg
                  </Combobox.ItemText>
                  <Combobox.ItemIndicator className={styles.ItemIndicator}>
                    <CheckIcon />
                  </Combobox.ItemIndicator>
                </Combobox.Item>
              ))
            )}
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.RootProvider>
  )
}

interface Character {
  name: string
  height: string
  mass: string
  created: string
  edited: string
  url: string
}
```

### Highlight Text

Highlight the matching search text in combobox items based on the user's input.

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { Highlight } from '@ark-ui/react/highlight'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
import { ChevronsUpDownIcon, XIcon } from 'lucide-react'
import styles from 'styles/combobox.module.css'

export const HighlightMatchingText = () => {
  const { contains } = useFilter({ sensitivity: 'base' })

  const { collection, filter } = useListCollection({
    initialItems: [
      { label: 'John Smith', value: 'john-smith' },
      { label: 'Jane Doe', value: 'jane-doe' },
      { label: 'Bob Johnson', value: 'bob-johnson' },
      { label: 'Alice Williams', value: 'alice-williams' },
      { label: 'Charlie Brown', value: 'charlie-brown' },
      { label: 'Diana Ross', value: 'diana-ross' },
    ],
    filter: contains,
  })

  const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
    filter(details.inputValue)
  }

  return (
    <Combobox.Root className={styles.Root} collection={collection} onInputValueChange={handleInputChange}>
      <Combobox.Label className={styles.Label}>Assignee</Combobox.Label>
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. John Smith" />
        <div className={styles.Indicators}>
          <Combobox.ClearTrigger className={styles.ClearTrigger}>
            <XIcon />
          </Combobox.ClearTrigger>
          <Combobox.Trigger className={styles.Trigger}>
            <ChevronsUpDownIcon />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            {collection.items.map((item) => (
              <Combobox.Item className={styles.Item} key={item.value} item={item}>
                <Combobox.ItemText className={styles.ItemText}>
                  <Combobox.Context>
                    {(context) => <Highlight text={item.label} query={context.inputValue} ignoreCase />}
                  </Combobox.Context>
                </Combobox.ItemText>
              </Combobox.Item>
            ))}
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.Root>
  )
}
```

### Dynamic

Generate combobox items dynamically based on user input. This is useful for creating suggestions or autocomplete
functionality.

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { Portal } from '@ark-ui/react/portal'
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react'
import styles from 'styles/combobox.module.css'

const suggestList = ['gmail.com', 'yahoo.com', 'ark-ui.com']

export const Dynamic = () => {
  const { collection, set } = useListCollection<string>({
    initialItems: [],
  })

  const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
    if (details.reason === 'input-change') {
      const items = suggestList.map((item) => `${details.inputValue}@${item}`)
      set(items)
    }
  }

  return (
    <Combobox.Root className={styles.Root} collection={collection} onInputValueChange={handleInputChange}>
      <Combobox.Label className={styles.Label}>Email</Combobox.Label>
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. john" />
        <div className={styles.Indicators}>
          <Combobox.Trigger className={styles.Trigger}>
            <ChevronsUpDownIcon />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            {collection.items.map((item) => (
              <Combobox.Item className={styles.Item} key={item} item={item}>
                <Combobox.ItemText className={styles.ItemText}>{item}</Combobox.ItemText>
                <Combobox.ItemIndicator className={styles.ItemIndicator}>
                  <CheckIcon />
                </Combobox.ItemIndicator>
              </Combobox.Item>
            ))}
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.Root>
  )
}
```

### Creatable

Allow users to create new options when their search doesn't match any existing items. This is useful for tags,
categories, or other custom values.

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
import { CheckIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react'
import { useState } from 'react'
import { flushSync } from 'react-dom'
import styles from 'styles/combobox.module.css'

interface Item {
  label: string
  value: string
  __new__?: boolean
}

const NEW_OPTION_VALUE = '[[new]]'
const createNewOption = (value: string): Item => ({ label: value, value: NEW_OPTION_VALUE })
const isNewOptionValue = (value: string) => value === NEW_OPTION_VALUE
const replaceNewOptionValue = (values: string[], value: string) =>
  values.map((v) => (v === NEW_OPTION_VALUE ? value : v))
const getNewOptionData = (inputValue: string): Item => ({ label: inputValue, value: inputValue, __new__: true })

export const Creatable = () => {
  const { contains } = useFilter({ sensitivity: 'base' })

  const { collection, filter, upsert, update, remove } = useListCollection<Item>({
    initialItems: [
      { label: 'Bug', value: 'bug' },
      { label: 'Feature', value: 'feature' },
      { label: 'Enhancement', value: 'enhancement' },
      { label: 'Documentation', value: 'docs' },
    ],
    filter: contains,
  })

  const isValidNewOption = (inputValue: string) => {
    const exactOptionMatch = collection.filter((item) => item.toLowerCase() === inputValue.toLowerCase()).size > 0
    return !exactOptionMatch && inputValue.trim().length > 0
  }

  const [selectedValue, setSelectedValue] = useState<string[]>([])
  const [inputValue, setInputValue] = useState('')

  const handleInputChange = ({ inputValue, reason }: Combobox.InputValueChangeDetails) => {
    if (reason === 'input-change' || reason === 'item-select') {
      flushSync(() => {
        if (isValidNewOption(inputValue)) {
          upsert(NEW_OPTION_VALUE, createNewOption(inputValue))
        } else if (inputValue.trim().length === 0) {
          remove(NEW_OPTION_VALUE)
        }
      })
      filter(inputValue)
    }
    setInputValue(inputValue)
  }

  const handleOpenChange = ({ reason }: Combobox.OpenChangeDetails) => {
    if (reason === 'trigger-click') {
      filter('')
    }
  }

  const handleValueChange = ({ value }: Combobox.ValueChangeDetails) => {
    setSelectedValue(replaceNewOptionValue(value, inputValue))
    if (value.includes(NEW_OPTION_VALUE)) {
      console.log('New Option Created', inputValue)
      update(NEW_OPTION_VALUE, getNewOptionData(inputValue))
    }
  }

  return (
    <Combobox.Root
      className={styles.Root}
      collection={collection}
      onInputValueChange={handleInputChange}
      onOpenChange={handleOpenChange}
      value={selectedValue}
      onValueChange={handleValueChange}
      allowCustomValue
    >
      <Combobox.Label className={styles.Label}>Label</Combobox.Label>
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. Bug" />
        <div className={styles.Indicators}>
          <Combobox.ClearTrigger className={styles.ClearTrigger}>
            <XIcon />
          </Combobox.ClearTrigger>
          <Combobox.Trigger className={styles.Trigger}>
            <ChevronsUpDownIcon />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            {collection.items.map((item) => (
              <Combobox.Item className={styles.Item} key={item.value} item={item}>
                {isNewOptionValue(item.value) ? (
                  <Combobox.ItemText className={styles.ItemText}>+ Create "{item.label}"</Combobox.ItemText>
                ) : (
                  <Combobox.ItemText className={styles.ItemText}>
                    {item.label} {item.__new__ ? '(new)' : ''}
                  </Combobox.ItemText>
                )}
                <Combobox.ItemIndicator className={styles.ItemIndicator}>
                  <CheckIcon />
                </Combobox.ItemIndicator>
              </Combobox.Item>
            ))}
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.Root>
  )
}
```

### Multiple Selection

Enable multiple selection by setting the `multiple` prop. Selected items can be displayed as tags above the input.

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react'
import { useRef } from 'react'
import styles from 'styles/combobox.module.css'

export const Multiple = () => {
  const { contains } = useFilter({ sensitivity: 'base' })

  const selectedValue = useRef<string[]>([])

  const { collection, filter, remove } = useListCollection({
    initialItems: [
      { label: 'JavaScript', value: 'js' },
      { label: 'TypeScript', value: 'ts' },
      { label: 'Python', value: 'python' },
      { label: 'Go', value: 'go' },
      { label: 'Rust', value: 'rust' },
      { label: 'Java', value: 'java' },
    ],
    filter: contains,
  })

  const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
    filter(details.inputValue)
  }

  const handleValueChange = (details: Combobox.ValueChangeDetails) => {
    selectedValue.current = details.value
    remove(...details.value)
  }

  return (
    <Combobox.Root
      className={styles.Root}
      collection={collection}
      onInputValueChange={handleInputChange}
      onValueChange={handleValueChange}
      multiple
    >
      <Combobox.Label className={styles.Label}>Skills</Combobox.Label>
      <Combobox.Context>
        {(combobox) => (
          <div className={styles.Tags}>
            {combobox.selectedItems.length === 0 && <span className={styles.TagPlaceholder}>None selected</span>}
            {combobox.selectedItems.map((item: any) => (
              <span key={item.value} className={styles.Tag}>
                {item.label}
              </span>
            ))}
          </div>
        )}
      </Combobox.Context>
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. JavaScript" />
        <div className={styles.Indicators}>
          <Combobox.Trigger className={styles.Trigger}>
            <ChevronsUpDownIcon />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            <Combobox.Empty className={styles.Item}>No skills found</Combobox.Empty>
            {collection.items.map((item) => (
              <Combobox.Item className={styles.Item} key={item.value} item={item}>
                <Combobox.ItemText className={styles.ItemText}>{item.label}</Combobox.ItemText>
                <Combobox.ItemIndicator className={styles.ItemIndicator}>
                  <CheckIcon />
                </Combobox.ItemIndicator>
              </Combobox.Item>
            ))}
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.Root>
  )
}
```

### Async Search

Load options asynchronously based on user input using the `useAsyncList` hook. This is useful for searching large
datasets or fetching data from an API.

```tsx
import { useAsyncList } from '@ark-ui/react/collection'
import { Combobox, createListCollection } from '@ark-ui/react/combobox'
import { Portal } from '@ark-ui/react/portal'
import { CheckIcon, ChevronsUpDownIcon, LoaderIcon, XIcon } from 'lucide-react'
import { startTransition } from 'react'
import styles from 'styles/combobox.module.css'

interface Movie {
  id: string
  title: string
  year: number
  director: string
  genre: string
}

export const AsyncSearch = () => {
  const list = useAsyncList<Movie>({
    async load({ filterText, signal }) {
      if (!filterText) return { items: [] }

      await new Promise((resolve) => setTimeout(resolve, 300))

      if (signal?.aborted) return { items: [] }

      const items = allMovies.filter(
        (movie) =>
          movie.title.toLowerCase().includes(filterText.toLowerCase()) ||
          movie.director.toLowerCase().includes(filterText.toLowerCase()) ||
          movie.genre.toLowerCase().includes(filterText.toLowerCase()),
      )

      return { items }
    },
  })

  const collection = createListCollection({
    items: list.items,
    itemToString: (item) => item.title,
    itemToValue: (item) => item.id,
  })

  const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
    if (details.reason === 'input-change') {
      startTransition(() => {
        list.setFilterText(details.inputValue)
      })
    }
  }

  return (
    <Combobox.Root className={styles.Root} collection={collection} onInputValueChange={handleInputChange}>
      <Combobox.Label className={styles.Label}>Movie</Combobox.Label>
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. Inception" />
        <div className={styles.Indicators}>
          <Combobox.ClearTrigger className={styles.ClearTrigger}>
            <XIcon />
          </Combobox.ClearTrigger>
          <Combobox.Trigger className={styles.Trigger}>
            <ChevronsUpDownIcon />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            {list.loading ? (
              <div className={styles.Status}>
                <LoaderIcon className={styles.Spinner} />
                <span>Searching...</span>
              </div>
            ) : list.error ? (
              <div className={styles.Status}>{list.error.message}</div>
            ) : list.items.length === 0 ? (
              <div className={styles.Status}>
                {list.filterText ? 'No results found' : 'Start typing to search movies...'}
              </div>
            ) : (
              collection.items.map((movie) => (
                <Combobox.Item className={styles.Item} key={movie.id} item={movie}>
                  <Combobox.ItemText className={styles.ItemText}>
                    <span className={styles.ItemTitle}>{movie.title}</span>
                    <span className={styles.ItemSubtitle}>
                      {movie.year} · {movie.director}
                    </span>
                  </Combobox.ItemText>
                  <Combobox.ItemIndicator className={styles.ItemIndicator}>
                    <CheckIcon />
                  </Combobox.ItemIndicator>
                </Combobox.Item>
              ))
            )}
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.Root>
  )
}

const allMovies: Movie[] = [
  { id: 'inception', title: 'Inception', year: 2010, director: 'Christopher Nolan', genre: 'Sci-Fi' },
  { id: 'the-dark-knight', title: 'The Dark Knight', year: 2008, director: 'Christopher Nolan', genre: 'Action' },
  { id: 'pulp-fiction', title: 'Pulp Fiction', year: 1994, director: 'Quentin Tarantino', genre: 'Crime' },
  { id: 'the-godfather', title: 'The Godfather', year: 1972, director: 'Francis Ford Coppola', genre: 'Crime' },
  { id: 'forrest-gump', title: 'Forrest Gump', year: 1994, director: 'Robert Zemeckis', genre: 'Drama' },
  { id: 'the-matrix', title: 'The Matrix', year: 1999, director: 'The Wachowskis', genre: 'Sci-Fi' },
  { id: 'interstellar', title: 'Interstellar', year: 2014, director: 'Christopher Nolan', genre: 'Sci-Fi' },
  { id: 'parasite', title: 'Parasite', year: 2019, director: 'Bong Joon-ho', genre: 'Thriller' },
  {
    id: 'the-shawshank-redemption',
    title: 'The Shawshank Redemption',
    year: 1994,
    director: 'Frank Darabont',
    genre: 'Drama',
  },
  { id: 'fight-club', title: 'Fight Club', year: 1999, director: 'David Fincher', genre: 'Drama' },
  { id: 'goodfellas', title: 'Goodfellas', year: 1990, director: 'Martin Scorsese', genre: 'Crime' },
  {
    id: 'the-silence-of-the-lambs',
    title: 'The Silence of the Lambs',
    year: 1991,
    director: 'Jonathan Demme',
    genre: 'Thriller',
  },
]
```

### Virtualized

For very large lists, use virtualization with `@tanstack/virtual` to render only the visible items. Pass the
`scrollToIndexFn` prop to enable keyboard navigation within the virtualized list.

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
import { useVirtualizer } from '@tanstack/react-virtual'
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react'
import { useRef } from 'react'
import { flushSync } from 'react-dom'
import styles from 'styles/combobox.module.css'

export const Virtualized = () => {
  const contentRef = useRef<HTMLDivElement | null>(null)

  const { startsWith } = useFilter({ sensitivity: 'base' })

  const { collection, filter, reset } = useListCollection({
    initialItems: countries,
    filter: startsWith,
  })

  const virtualizer = useVirtualizer({
    count: collection.size,
    getScrollElement: () => contentRef.current,
    estimateSize: () => 32,
    overscan: 10,
  })

  const handleScrollToIndex: Combobox.RootProps<Country>['scrollToIndexFn'] = (details) => {
    flushSync(() => {
      virtualizer.scrollToIndex(details.index, {
        align: 'center',
        behavior: 'auto',
      })
    })
  }

  const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
    filter(details.inputValue)
  }

  return (
    <Combobox.Root
      className={styles.Root}
      collection={collection}
      onInputValueChange={handleInputChange}
      scrollToIndexFn={handleScrollToIndex}
    >
      <Combobox.Label className={styles.Label}>Country</Combobox.Label>
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. Germany" />
        <div className={styles.Indicators}>
          <Combobox.Trigger className={styles.Trigger} onClick={reset}>
            <ChevronsUpDownIcon />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            <div
              ref={contentRef}
              className={styles.Scroller}
              style={{ ['--total-size' as string]: `${virtualizer.getTotalSize()}px` }}
            >
              <div style={{ height: virtualizer.getTotalSize(), width: '100%', position: 'relative' }}>
                {virtualizer.getVirtualItems().map((virtualItem) => {
                  const item = collection.items[virtualItem.index]
                  return (
                    <Combobox.Item
                      className={styles.Item}
                      key={item.value}
                      item={item}
                      aria-setsize={collection.size}
                      aria-posinset={virtualItem.index + 1}
                      style={{
                        position: 'absolute',
                        top: 0,
                        left: 0,
                        width: '100%',
                        height: `${virtualItem.size}px`,
                        transform: `translateY(${virtualItem.start}px)`,
                      }}
                    >
                      <Combobox.ItemText className={styles.ItemText}>
                        <span aria-hidden style={{ marginRight: 8 }}>
                          {item.emoji}
                        </span>
                        {item.label}
                      </Combobox.ItemText>
                      <Combobox.ItemIndicator className={styles.ItemIndicator}>
                        <CheckIcon />
                      </Combobox.ItemIndicator>
                    </Combobox.Item>
                  )
                })}
              </div>
            </div>
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.Root>
  )
}

interface Country {
  value: string
  label: string
  emoji: string
}

const countries: Country[] = [
  { value: 'AD', label: 'Andorra', emoji: '🇦🇩' },
  { value: 'AE', label: 'United Arab Emirates', emoji: '🇦🇪' },
  { value: 'AF', label: 'Afghanistan', emoji: '🇦🇫' },
  { value: 'AG', label: 'Antigua and Barbuda', emoji: '🇦🇬' },
  { value: 'AI', label: 'Anguilla', emoji: '🇦🇮' },
  { value: 'AL', label: 'Albania', emoji: '🇦🇱' },
  { value: 'AM', label: 'Armenia', emoji: '🇦🇲' },
  { value: 'AO', label: 'Angola', emoji: '🇦🇴' },
  { value: 'AQ', label: 'Antarctica', emoji: '🇦🇶' },
  { value: 'AR', label: 'Argentina', emoji: '🇦🇷' },
  { value: 'AS', label: 'American Samoa', emoji: '🇦🇸' },
  { value: 'AT', label: 'Austria', emoji: '🇦🇹' },
  { value: 'AU', label: 'Australia', emoji: '🇦🇺' },
  { value: 'AW', label: 'Aruba', emoji: '🇦🇼' },
  { value: 'AX', label: 'Åland Islands', emoji: '🇦🇽' },
  { value: 'AZ', label: 'Azerbaijan', emoji: '🇦🇿' },
  { value: 'BA', label: 'Bosnia and Herzegovina', emoji: '🇧🇦' },
  { value: 'BB', label: 'Barbados', emoji: '🇧🇧' },
  { value: 'BD', label: 'Bangladesh', emoji: '🇧🇩' },
  { value: 'BE', label: 'Belgium', emoji: '🇧🇪' },
  { value: 'BF', label: 'Burkina Faso', emoji: '🇧🇫' },
  { value: 'BG', label: 'Bulgaria', emoji: '🇧🇬' },
  { value: 'BH', label: 'Bahrain', emoji: '🇧🇭' },
  { value: 'BI', label: 'Burundi', emoji: '🇧🇮' },
  { value: 'BJ', label: 'Benin', emoji: '🇧🇯' },
  { value: 'BL', label: 'Saint Barthélemy', emoji: '🇧🇱' },
  { value: 'BM', label: 'Bermuda', emoji: '🇧🇲' },
  { value: 'BN', label: 'Brunei', emoji: '🇧🇳' },
  { value: 'BO', label: 'Bolivia', emoji: '🇧🇴' },
  { value: 'BR', label: 'Brazil', emoji: '🇧🇷' },
  { value: 'BS', label: 'Bahamas', emoji: '🇧🇸' },
  { value: 'BT', label: 'Bhutan', emoji: '🇧🇹' },
  { value: 'BW', label: 'Botswana', emoji: '🇧🇼' },
  { value: 'BY', label: 'Belarus', emoji: '🇧🇾' },
  { value: 'BZ', label: 'Belize', emoji: '🇧🇿' },
  { value: 'CA', label: 'Canada', emoji: '🇨🇦' },
  { value: 'CD', label: 'Congo', emoji: '🇨🇩' },
  { value: 'CF', label: 'Central African Republic', emoji: '🇨🇫' },
  { value: 'CH', label: 'Switzerland', emoji: '🇨🇭' },
  { value: 'CI', label: "Côte d'Ivoire", emoji: '🇨🇮' },
  { value: 'CK', label: 'Cook Islands', emoji: '🇨🇰' },
  { value: 'CL', label: 'Chile', emoji: '🇨🇱' },
  { value: 'CM', label: 'Cameroon', emoji: '🇨🇲' },
  { value: 'CN', label: 'China', emoji: '🇨🇳' },
  { value: 'CO', label: 'Colombia', emoji: '🇨🇴' },
  { value: 'CR', label: 'Costa Rica', emoji: '🇨🇷' },
  { value: 'CU', label: 'Cuba', emoji: '🇨🇺' },
  { value: 'CV', label: 'Cabo Verde', emoji: '🇨🇻' },
  { value: 'CY', label: 'Cyprus', emoji: '🇨🇾' },
  { value: 'CZ', label: 'Czech Republic', emoji: '🇨🇿' },
  { value: 'DE', label: 'Germany', emoji: '🇩🇪' },
  { value: 'DJ', label: 'Djibouti', emoji: '🇩🇯' },
  { value: 'DK', label: 'Denmark', emoji: '🇩🇰' },
  { value: 'DM', label: 'Dominica', emoji: '🇩🇲' },
  { value: 'DO', label: 'Dominican Republic', emoji: '🇩🇴' },
  { value: 'DZ', label: 'Algeria', emoji: '🇩🇿' },
  { value: 'EC', label: 'Ecuador', emoji: '🇪🇨' },
  { value: 'EE', label: 'Estonia', emoji: '🇪🇪' },
  { value: 'EG', label: 'Egypt', emoji: '🇪🇬' },
  { value: 'ER', label: 'Eritrea', emoji: '🇪🇷' },
  { value: 'ES', label: 'Spain', emoji: '🇪🇸' },
  { value: 'ET', label: 'Ethiopia', emoji: '🇪🇹' },
  { value: 'FI', label: 'Finland', emoji: '🇫🇮' },
  { value: 'FJ', label: 'Fiji', emoji: '🇫🇯' },
  { value: 'FK', label: 'Falkland Islands', emoji: '🇫🇰' },
  { value: 'FM', label: 'Micronesia', emoji: '🇫🇲' },
  { value: 'FO', label: 'Faroe Islands', emoji: '🇫🇴' },
  { value: 'FR', label: 'France', emoji: '🇫🇷' },
  { value: 'GA', label: 'Gabon', emoji: '🇬🇦' },
  { value: 'GB', label: 'United Kingdom', emoji: '🇬🇧' },
  { value: 'GD', label: 'Grenada', emoji: '🇬🇩' },
  { value: 'GE', label: 'Georgia', emoji: '🇬🇪' },
  { value: 'GH', label: 'Ghana', emoji: '🇬🇭' },
  { value: 'GI', label: 'Gibraltar', emoji: '🇬🇮' },
  { value: 'GL', label: 'Greenland', emoji: '🇬🇱' },
  { value: 'GM', label: 'Gambia', emoji: '🇬🇲' },
  { value: 'GN', label: 'Guinea', emoji: '🇬🇳' },
  { value: 'GQ', label: 'Equatorial Guinea', emoji: '🇬🇶' },
  { value: 'GR', label: 'Greece', emoji: '🇬🇷' },
  { value: 'GT', label: 'Guatemala', emoji: '🇬🇹' },
  { value: 'GU', label: 'Guam', emoji: '🇬🇺' },
  { value: 'GW', label: 'Guinea-Bissau', emoji: '🇬🇼' },
  { value: 'GY', label: 'Guyana', emoji: '🇬🇾' },
  { value: 'HK', label: 'Hong Kong', emoji: '🇭🇰' },
  { value: 'HN', label: 'Honduras', emoji: '🇭🇳' },
  { value: 'HR', label: 'Croatia', emoji: '🇭🇷' },
  { value: 'HT', label: 'Haiti', emoji: '🇭🇹' },
  { value: 'HU', label: 'Hungary', emoji: '🇭🇺' },
  { value: 'ID', label: 'Indonesia', emoji: '🇮🇩' },
  { value: 'IE', label: 'Ireland', emoji: '🇮🇪' },
  { value: 'IL', label: 'Israel', emoji: '🇮🇱' },
  { value: 'IM', label: 'Isle of Man', emoji: '🇮🇲' },
  { value: 'IN', label: 'India', emoji: '🇮🇳' },
  { value: 'IQ', label: 'Iraq', emoji: '🇮🇶' },
  { value: 'IR', label: 'Iran', emoji: '🇮🇷' },
  { value: 'IS', label: 'Iceland', emoji: '🇮🇸' },
  { value: 'IT', label: 'Italy', emoji: '🇮🇹' },
  { value: 'JE', label: 'Jersey', emoji: '🇯🇪' },
  { value: 'JM', label: 'Jamaica', emoji: '🇯🇲' },
  { value: 'JO', label: 'Jordan', emoji: '🇯🇴' },
  { value: 'JP', label: 'Japan', emoji: '🇯🇵' },
  { value: 'KE', label: 'Kenya', emoji: '🇰🇪' },
  { value: 'KG', label: 'Kyrgyzstan', emoji: '🇰🇬' },
  { value: 'KH', label: 'Cambodia', emoji: '🇰🇭' },
  { value: 'KI', label: 'Kiribati', emoji: '🇰🇮' },
  { value: 'KM', label: 'Comoros', emoji: '🇰🇲' },
  { value: 'KN', label: 'Saint Kitts and Nevis', emoji: '🇰🇳' },
  { value: 'KP', label: 'North Korea', emoji: '🇰🇵' },
  { value: 'KR', label: 'South Korea', emoji: '🇰🇷' },
  { value: 'KW', label: 'Kuwait', emoji: '🇰🇼' },
  { value: 'KY', label: 'Cayman Islands', emoji: '🇰🇾' },
  { value: 'KZ', label: 'Kazakhstan', emoji: '🇰🇿' },
  { value: 'LA', label: 'Laos', emoji: '🇱🇦' },
  { value: 'LB', label: 'Lebanon', emoji: '🇱🇧' },
  { value: 'LC', label: 'Saint Lucia', emoji: '🇱🇨' },
  { value: 'LI', label: 'Liechtenstein', emoji: '🇱🇮' },
  { value: 'LK', label: 'Sri Lanka', emoji: '🇱🇰' },
  { value: 'LR', label: 'Liberia', emoji: '🇱🇷' },
  { value: 'LS', label: 'Lesotho', emoji: '🇱🇸' },
  { value: 'LT', label: 'Lithuania', emoji: '🇱🇹' },
  { value: 'LU', label: 'Luxembourg', emoji: '🇱🇺' },
  { value: 'LV', label: 'Latvia', emoji: '🇱🇻' },
  { value: 'LY', label: 'Libya', emoji: '🇱🇾' },
  { value: 'MA', label: 'Morocco', emoji: '🇲🇦' },
  { value: 'MC', label: 'Monaco', emoji: '🇲🇨' },
  { value: 'MD', label: 'Moldova', emoji: '🇲🇩' },
  { value: 'ME', label: 'Montenegro', emoji: '🇲🇪' },
  { value: 'MG', label: 'Madagascar', emoji: '🇲🇬' },
  { value: 'MH', label: 'Marshall Islands', emoji: '🇲🇭' },
  { value: 'MK', label: 'North Macedonia', emoji: '🇲🇰' },
  { value: 'ML', label: 'Mali', emoji: '🇲🇱' },
  { value: 'MM', label: 'Myanmar', emoji: '🇲🇲' },
  { value: 'MN', label: 'Mongolia', emoji: '🇲🇳' },
  { value: 'MO', label: 'Macao', emoji: '🇲🇴' },
  { value: 'MR', label: 'Mauritania', emoji: '🇲🇷' },
  { value: 'MS', label: 'Montserrat', emoji: '🇲🇸' },
  { value: 'MT', label: 'Malta', emoji: '🇲🇹' },
  { value: 'MU', label: 'Mauritius', emoji: '🇲🇺' },
  { value: 'MV', label: 'Maldives', emoji: '🇲🇻' },
  { value: 'MW', label: 'Malawi', emoji: '🇲🇼' },
  { value: 'MX', label: 'Mexico', emoji: '🇲🇽' },
  { value: 'MY', label: 'Malaysia', emoji: '🇲🇾' },
  { value: 'MZ', label: 'Mozambique', emoji: '🇲🇿' },
  { value: 'NA', label: 'Namibia', emoji: '🇳🇦' },
  { value: 'NC', label: 'New Caledonia', emoji: '🇳🇨' },
  { value: 'NE', label: 'Niger', emoji: '🇳🇪' },
  { value: 'NF', label: 'Norfolk Island', emoji: '🇳🇫' },
  { value: 'NG', label: 'Nigeria', emoji: '🇳🇬' },
  { value: 'NI', label: 'Nicaragua', emoji: '🇳🇮' },
  { value: 'NL', label: 'Netherlands', emoji: '🇳🇱' },
  { value: 'NO', label: 'Norway', emoji: '🇳🇴' },
  { value: 'NP', label: 'Nepal', emoji: '🇳🇵' },
  { value: 'NR', label: 'Nauru', emoji: '🇳🇷' },
  { value: 'NU', label: 'Niue', emoji: '🇳🇺' },
  { value: 'NZ', label: 'New Zealand', emoji: '🇳🇿' },
  { value: 'OM', label: 'Oman', emoji: '🇴🇲' },
  { value: 'PA', label: 'Panama', emoji: '🇵🇦' },
  { value: 'PE', label: 'Peru', emoji: '🇵🇪' },
  { value: 'PF', label: 'French Polynesia', emoji: '🇵🇫' },
  { value: 'PG', label: 'Papua New Guinea', emoji: '🇵🇬' },
  { value: 'PH', label: 'Philippines', emoji: '🇵🇭' },
  { value: 'PK', label: 'Pakistan', emoji: '🇵🇰' },
  { value: 'PL', label: 'Poland', emoji: '🇵🇱' },
  { value: 'PR', label: 'Puerto Rico', emoji: '🇵🇷' },
  { value: 'PS', label: 'Palestine', emoji: '🇵🇸' },
  { value: 'PT', label: 'Portugal', emoji: '🇵🇹' },
  { value: 'PW', label: 'Palau', emoji: '🇵🇼' },
  { value: 'PY', label: 'Paraguay', emoji: '🇵🇾' },
  { value: 'QA', label: 'Qatar', emoji: '🇶🇦' },
  { value: 'RO', label: 'Romania', emoji: '🇷🇴' },
  { value: 'RS', label: 'Serbia', emoji: '🇷🇸' },
  { value: 'RU', label: 'Russia', emoji: '🇷🇺' },
  { value: 'RW', label: 'Rwanda', emoji: '🇷🇼' },
  { value: 'SA', label: 'Saudi Arabia', emoji: '🇸🇦' },
  { value: 'SB', label: 'Solomon Islands', emoji: '🇸🇧' },
  { value: 'SC', label: 'Seychelles', emoji: '🇸🇨' },
  { value: 'SD', label: 'Sudan', emoji: '🇸🇩' },
  { value: 'SE', label: 'Sweden', emoji: '🇸🇪' },
  { value: 'SG', label: 'Singapore', emoji: '🇸🇬' },
  { value: 'SI', label: 'Slovenia', emoji: '🇸🇮' },
  { value: 'SK', label: 'Slovakia', emoji: '🇸🇰' },
  { value: 'SL', label: 'Sierra Leone', emoji: '🇸🇱' },
  { value: 'SM', label: 'San Marino', emoji: '🇸🇲' },
  { value: 'SN', label: 'Senegal', emoji: '🇸🇳' },
  { value: 'SO', label: 'Somalia', emoji: '🇸🇴' },
  { value: 'SR', label: 'Suriname', emoji: '🇸🇷' },
  { value: 'SS', label: 'South Sudan', emoji: '🇸🇸' },
  { value: 'ST', label: 'Sao Tome and Principe', emoji: '🇸🇹' },
  { value: 'SV', label: 'El Salvador', emoji: '🇸🇻' },
  { value: 'SY', label: 'Syria', emoji: '🇸🇾' },
  { value: 'SZ', label: 'Eswatini', emoji: '🇸🇿' },
  { value: 'TC', label: 'Turks and Caicos Islands', emoji: '🇹🇨' },
  { value: 'TD', label: 'Chad', emoji: '🇹🇩' },
  { value: 'TG', label: 'Togo', emoji: '🇹🇬' },
  { value: 'TH', label: 'Thailand', emoji: '🇹🇭' },
  { value: 'TJ', label: 'Tajikistan', emoji: '🇹🇯' },
  { value: 'TK', label: 'Tokelau', emoji: '🇹🇰' },
  { value: 'TL', label: 'Timor-Leste', emoji: '🇹🇱' },
  { value: 'TM', label: 'Turkmenistan', emoji: '🇹🇲' },
  { value: 'TN', label: 'Tunisia', emoji: '🇹🇳' },
  { value: 'TO', label: 'Tonga', emoji: '🇹🇴' },
  { value: 'TR', label: 'Türkiye', emoji: '🇹🇷' },
  { value: 'TT', label: 'Trinidad and Tobago', emoji: '🇹🇹' },
  { value: 'TV', label: 'Tuvalu', emoji: '🇹🇻' },
  { value: 'TW', label: 'Taiwan', emoji: '🇹🇼' },
  { value: 'TZ', label: 'Tanzania', emoji: '🇹🇿' },
  { value: 'UA', label: 'Ukraine', emoji: '🇺🇦' },
  { value: 'UG', label: 'Uganda', emoji: '🇺🇬' },
  { value: 'US', label: 'United States', emoji: '🇺🇸' },
  { value: 'UY', label: 'Uruguay', emoji: '🇺🇾' },
  { value: 'UZ', label: 'Uzbekistan', emoji: '🇺🇿' },
  { value: 'VA', label: 'Vatican City', emoji: '🇻🇦' },
  { value: 'VC', label: 'Saint Vincent and the Grenadines', emoji: '🇻🇨' },
  { value: 'VE', label: 'Venezuela', emoji: '🇻🇪' },
  { value: 'VG', label: 'British Virgin Islands', emoji: '🇻🇬' },
  { value: 'VI', label: 'U.S. Virgin Islands', emoji: '🇻🇮' },
  { value: 'VN', label: 'Vietnam', emoji: '🇻🇳' },
  { value: 'VU', label: 'Vanuatu', emoji: '🇻🇺' },
  { value: 'WF', label: 'Wallis and Futuna', emoji: '🇼🇫' },
  { value: 'WS', label: 'Samoa', emoji: '🇼🇸' },
  { value: 'YE', label: 'Yemen', emoji: '🇾🇪' },
  { value: 'YT', label: 'Mayotte', emoji: '🇾🇹' },
  { value: 'ZA', label: 'South Africa', emoji: '🇿🇦' },
  { value: 'ZM', label: 'Zambia', emoji: '🇿🇲' },
  { value: 'ZW', label: 'Zimbabwe', emoji: '🇿🇼' },
]
```

### Custom Object

Use the `itemToString` and `itemToValue` props to map custom objects to the required interface.

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
import { CheckIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react'
import styles from 'styles/combobox.module.css'

export const CustomObject = () => {
  const { contains } = useFilter({ sensitivity: 'base' })

  const { collection, filter } = useListCollection({
    initialItems: [
      { country: 'United States', code: 'US', flag: '🇺🇸' },
      { country: 'Canada', code: 'CA', flag: '🇨🇦' },
      { country: 'Australia', code: 'AU', flag: '🇦🇺' },
    ],
    itemToString: (item) => item.country,
    itemToValue: (item) => item.code,
    filter: contains,
  })

  const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
    filter(details.inputValue)
  }

  return (
    <Combobox.Root className={styles.Root} collection={collection} onInputValueChange={handleInputChange}>
      <Combobox.Label className={styles.Label}>Country</Combobox.Label>
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. Canada" />
        <div className={styles.Indicators}>
          <Combobox.ClearTrigger className={styles.ClearTrigger}>
            <XIcon />
          </Combobox.ClearTrigger>
          <Combobox.Trigger className={styles.Trigger}>
            <ChevronsUpDownIcon />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            {collection.items.map((item) => (
              <Combobox.Item className={styles.Item} key={item.code} item={item}>
                <Combobox.ItemText className={styles.ItemText}>
                  {item.flag} {item.country}
                </Combobox.ItemText>
                <Combobox.ItemIndicator className={styles.ItemIndicator}>
                  <CheckIcon />
                </Combobox.ItemIndicator>
              </Combobox.Item>
            ))}
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.Root>
  )
}
```

### Limit Results

Use the `limit` property on `useListCollection` to limit the number of rendered items in the DOM.

```tsx
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react'
import styles from 'styles/combobox.module.css'

const cities = [
  { label: 'New York', value: 'new-york' },
  { label: 'Los Angeles', value: 'los-angeles' },
  { label: 'Chicago', value: 'chicago' },
  { label: 'Houston', value: 'houston' },
  { label: 'Phoenix', value: 'phoenix' },
  { label: 'Philadelphia', value: 'philadelphia' },
  { label: 'San Antonio', value: 'san-antonio' },
  { label: 'San Diego', value: 'san-diego' },
  { label: 'Dallas', value: 'dallas' },
  { label: 'San Jose', value: 'san-jose' },
  { label: 'Austin', value: 'austin' },
  { label: 'Jacksonville', value: 'jacksonville' },
  { label: 'Fort Worth', value: 'fort-worth' },
  { label: 'Columbus', value: 'columbus' },
  { label: 'Charlotte', value: 'charlotte' },
  { label: 'San Francisco', value: 'san-francisco' },
  { label: 'Indianapolis', value: 'indianapolis' },
  { label: 'Seattle', value: 'seattle' },
  { label: 'Denver', value: 'denver' },
  { label: 'Boston', value: 'boston' },
]

export const LimitResults = () => {
  const { contains } = useFilter({ sensitivity: 'base' })

  const { collection, filter } = useListCollection({
    initialItems: cities,
    limit: 5,
    filter: contains,
  })

  const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
    filter(details.inputValue)
  }

  return (
    <Combobox.Root className={styles.Root} collection={collection} onInputValueChange={handleInputChange}>
      <Combobox.Label className={styles.Label}>City</Combobox.Label>
      <Combobox.Control className={styles.Control}>
        <Combobox.Input className={styles.Input} placeholder="e.g. New York" />
        <div className={styles.Indicators}>
          <Combobox.Trigger className={styles.Trigger}>
            <ChevronsUpDownIcon />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className={styles.Content}>
            {collection.items.map((item) => (
              <Combobox.Item className={styles.Item} key={item.value} item={item}>
                <Combobox.ItemText className={styles.ItemText}>{item.label}</Combobox.ItemText>
                <Combobox.ItemIndicator className={styles.ItemIndicator}>
                  <CheckIcon />
                </Combobox.ItemIndicator>
              </Combobox.Item>
            ))}
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.Root>
  )
}
```

## Guides

### Router Links

Customize the `navigate` prop on `Combobox.Root` to integrate with your router. Using Tanstack Router:

```tsx
import { Combobox } from '@ark-ui/<framework>/combobox'
import { useNavigate } from '@tanstack/react-router'

function Demo() {
  const navigate = useNavigate()
  return (
    <Combobox.Root
      navigate={(e) => {
        navigate({ to: e.node.href })
      }}
    >
      {/* ... */}
    </Combobox.Root>
  )
}
```

### Custom Objects

By default, the combobox collection expects an array of objects with `label` and `value` properties. In some cases, you
may need to deal with custom objects.

Use the `itemToString` and `itemToValue` props to map the custom object to the required interface.

```tsx
const items = [
  { country: 'United States', code: 'US', flag: '🇺🇸' },
  { country: 'Canada', code: 'CA', flag: '🇨🇦' },
  { country: 'Australia', code: 'AU', flag: '🇦🇺' },
  // ...
]

const { collection } = useListCollection({
  initialItems: items,
  itemToString: (item) => item.country,
  itemToValue: (item) => item.code,
})
```

### Type Safety

The `Combobox.RootComponent` type enables you to create typed wrapper components that maintain full type safety for
collection items.

```tsx
const Combobox: ArkCombobox.RootComponent = (props) => {
  return <ArkCombobox.Root {...props}>{/* ... */}</ArkCombobox.Root>
}
```

Use the wrapper with full type inference on `onValueChange` and other callbacks:

```tsx
const App = () => {
  const { collection } = useListCollection({
    initialItems: [
      { label: 'React', value: 'react' },
      { label: 'Vue', value: 'vue' },
    ],
  })
  return (
    <Combobox
      collection={collection}
      onValueChange={(e) => {
        // e.items is typed as Array<{ label: string, value: string }>
        console.log(e.items)
      }}
    >
      {/* ... */}
    </Combobox>
  )
}
```

### Large Datasets

The recommended way of managing large lists is to use the `limit` property on the `useListCollection` hook. This will
limit the number of rendered items in the DOM to improve performance.

```tsx {3}
const { collection } = useListCollection({
  initialItems: items,
  limit: 10,
})
```

### Available Size

The following css variables are exposed to the `Combobox.Positioner` which you can use to style the `Combobox.Content`

```css
/* width of the combobox control */
--reference-width: <pixel-value>;
/* width of the available viewport */
--available-width: <pixel-value>;
/* height of the available viewport */
--available-height: <pixel-value>;
```

For example, if you want to make sure the maximum height doesn't exceed the available height, you can use the following:

```css
[data-scope='combobox'][data-part='content'] {
  max-height: calc(var(--available-height) - 100px);
}
```

## API Reference

### Props

### Root

#### Props

**`collection`**
Type: `ListCollection<T>`
Required: true
Default Value: `undefined`
Description: The collection of items

**`allowCustomValue`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether to allow typing custom values in the input

**`alwaysSubmitOnEnter`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether to always submit on Enter key press, even if popup is open.
Useful for single-field autocomplete forms where Enter should submit the form.

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

**`autoFocus`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether to autofocus the input on mount

**`closeOnSelect`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether to close the combobox when an item is selected.

**`composite`**
Type: `boolean`
Required: false
Default Value: `true`
Description: Whether the combobox is a composed with other composite widgets like tabs

**`defaultHighlightedValue`**
Type: `string`
Required: false
Default Value: `undefined`
Description: The initial highlighted value of the combobox when rendered.
Use when you don't need to control the highlighted value of the combobox.

**`defaultInputValue`**
Type: `string`
Required: false
Default Value: `""`
Description: The initial value of the combobox's input when rendered.
Use when you don't need to control the value of the combobox's input.

**`defaultOpen`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: The initial open state of the combobox when rendered.
Use when you don't need to control the open state of the combobox.

**`defaultValue`**
Type: `string[]`
Required: false
Default Value: `[]`
Description: The initial value of the combobox's selected items when rendered.
Use when you don't need to control the value of the combobox's selected items.

**`disabled`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether the combobox is disabled

**`disableLayer`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether to disable registering this a dismissable layer

**`form`**
Type: `string`
Required: false
Default Value: `undefined`
Description: The associate form of the combobox.

**`hideMode`**
Type: `HideMode`
Required: false
Default Value: `'display-none'`
Description: How to hide content when mounted but not present.
- `'display-none'`: HTML `hidden` attribute. Effects stay alive.
- `'activity'`: React 19 `<Activity mode="hidden">`. Effects pause. Requires React 19+.

**`highlightedValue`**
Type: `string`
Required: false
Default Value: `undefined`
Description: The controlled highlighted value of the combobox

**`id`**
Type: `string`
Required: false
Default Value: `undefined`
Description: The unique identifier of the machine.

**`ids`**
Type: `Partial<{
  root: string
  label: string
  control: string
  input: string
  content: string
  trigger: string
  clearTrigger: string
  item: (id: string, index?: number | undefined) => string
  positioner: string
  itemGroup: (id: string | number) => string
  itemGroupLabel: (id: string | number) => string
}>`
Required: false
Default Value: `undefined`
Description: The ids of the elements in the combobox. Useful for composition.

**`immediate`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether to synchronize the present change immediately or defer it to the next frame

**`inputBehavior`**
Type: `'none' | 'autohighlight' | 'autocomplete'`
Required: false
Default Value: `"none"`
Description: Defines the auto-completion behavior of the combobox.

- `autohighlight`: The first focused item is highlighted as the user types
- `autocomplete`: Navigating the listbox with the arrow keys selects the item and the input is updated

**`inputValue`**
Type: `string`
Required: false
Default Value: `undefined`
Description: The controlled value of the combobox's input

**`invalid`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether the combobox is invalid

**`lazyMount`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether to enable lazy mounting

**`loopFocus`**
Type: `boolean`
Required: false
Default Value: `true`
Description: Whether to loop the keyboard navigation through the items

**`multiple`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether to allow multiple selection.

**Good to know:** When `multiple` is `true`, the `selectionBehavior` is automatically set to `clear`.
It is recommended to render the selected items in a separate container.

**`name`**
Type: `string`
Required: false
Default Value: `undefined`
Description: The `name` attribute of the combobox's input. Useful for form submission

**`navigate`**
Type: `(details: NavigateDetails) => void`
Required: false
Default Value: `undefined`
Description: Function to navigate to the selected item

**`onExitComplete`**
Type: `VoidFunction`
Required: false
Default Value: `undefined`
Description: Function called when the animation ends in the closed state

**`onFocusOutside`**
Type: `(event: FocusOutsideEvent) => void`
Required: false
Default Value: `undefined`
Description: Function called when the focus is moved outside the component

**`onHighlightChange`**
Type: `(details: HighlightChangeDetails<T>) => void`
Required: false
Default Value: `undefined`
Description: Function called when an item is highlighted using the pointer
or keyboard navigation.

**`onInputValueChange`**
Type: `(details: InputValueChangeDetails) => void`
Required: false
Default Value: `undefined`
Description: Function called when the input's value changes

**`onInteractOutside`**
Type: `(event: InteractOutsideEvent) => void`
Required: false
Default Value: `undefined`
Description: Function called when an interaction happens outside the component

**`onOpenChange`**
Type: `(details: OpenChangeDetails) => void`
Required: false
Default Value: `undefined`
Description: Function called when the popup is opened

**`onPointerDownOutside`**
Type: `(event: PointerDownOutsideEvent) => void`
Required: false
Default Value: `undefined`
Description: Function called when the pointer is pressed down outside the component

**`onSelect`**
Type: `(details: SelectionDetails) => void`
Required: false
Default Value: `undefined`
Description: Function called when an item is selected

**`onValueChange`**
Type: `(details: ValueChangeDetails<T>) => void`
Required: false
Default Value: `undefined`
Description: Function called when a new item is selected

**`open`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: The controlled open state of the combobox

**`openOnChange`**
Type: `boolean | ((details: InputValueChangeDetails) => boolean)`
Required: false
Default Value: `true`
Description: Whether to show the combobox when the input value changes

**`openOnClick`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether to open the combobox popup on initial click on the input

**`openOnKeyPress`**
Type: `boolean`
Required: false
Default Value: `true`
Description: Whether to open the combobox on arrow key press

**`placeholder`**
Type: `string`
Required: false
Default Value: `undefined`
Description: The placeholder text of the combobox's input

**`positioning`**
Type: `PositioningOptions`
Required: false
Default Value: `{ placement: "bottom-start" }`
Description: The positioning options to dynamically position the menu

**`present`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether the node is present (controlled by the user)

**`readOnly`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether the combobox is readonly. This puts the combobox in a "non-editable" mode
but the user can still interact with it

**`required`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether the combobox is required

**`scrollToIndexFn`**
Type: `(details: ScrollToIndexDetails) => void`
Required: false
Default Value: `undefined`
Description: Function to scroll to a specific index

**`selectionBehavior`**
Type: `'clear' | 'replace' | 'preserve'`
Required: false
Default Value: `"replace"`
Description: The behavior of the combobox input when an item is selected

- `replace`: The selected item string is set as the input value
- `clear`: The input value is cleared
- `preserve`: The input value is preserved

**`skipAnimationOnMount`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether to allow the initial presence animation.

**`translations`**
Type: `IntlTranslations`
Required: false
Default Value: `undefined`
Description: Specifies the localized strings that identifies the accessibility elements and their states

**`unmountOnExit`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether to unmount on exit.

**`value`**
Type: `string[]`
Required: false
Default Value: `undefined`
Description: The controlled value of the combobox's selected items

#### Data Attributes

**`data-scope`**: combobox
**`data-part`**: root
**`data-invalid`**: Present when invalid
**`data-readonly`**: Present when read-only

### ClearTrigger

#### 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.

#### Data Attributes

**`data-scope`**: combobox
**`data-part`**: clear-trigger
**`data-invalid`**: Present when invalid

### Content

#### 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.

#### Data Attributes

**`data-scope`**: combobox
**`data-part`**: content
**`data-state`**: "open" | "closed"
**`data-nested`**: listbox
**`data-has-nested`**: listbox
**`data-placement`**: The placement of the content
**`data-side`**: The side of the trigger that the content is positioned on
**`data-empty`**: Present when the content is empty

### Control

#### 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.

#### Data Attributes

**`data-scope`**: combobox
**`data-part`**: control
**`data-state`**: "open" | "closed"
**`data-focus`**: Present when focused
**`data-disabled`**: Present when disabled
**`data-invalid`**: Present when invalid

### Empty

#### 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.

### Input

#### 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.

#### Data Attributes

**`data-scope`**: combobox
**`data-part`**: input
**`data-invalid`**: Present when invalid
**`data-autofocus`**: 
**`data-state`**: "open" | "closed"

### ItemGroupLabel

#### 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.

### ItemGroup

#### 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.

#### Data Attributes

**`data-scope`**: combobox
**`data-part`**: item-group
**`data-empty`**: Present when the content is empty

### ItemIndicator

#### 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.

#### Data Attributes

**`data-scope`**: combobox
**`data-part`**: item-indicator
**`data-state`**: "checked" | "unchecked"

### Item

#### 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.

**`item`**
Type: `any`
Required: false
Default Value: `undefined`
Description: The item to render

**`persistFocus`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether hovering outside should clear the highlighted state

#### Data Attributes

**`data-scope`**: combobox
**`data-part`**: item
**`data-highlighted`**: Present when highlighted
**`data-state`**: "checked" | "unchecked"
**`data-disabled`**: Present when disabled
**`data-value`**: The value of the item

### ItemText

#### 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.

#### Data Attributes

**`data-scope`**: combobox
**`data-part`**: item-text
**`data-state`**: "checked" | "unchecked"
**`data-disabled`**: Present when disabled
**`data-highlighted`**: Present when highlighted

### Label

#### 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.

#### Data Attributes

**`data-scope`**: combobox
**`data-part`**: label
**`data-readonly`**: Present when read-only
**`data-disabled`**: Present when disabled
**`data-invalid`**: Present when invalid
**`data-required`**: Present when required
**`data-focus`**: Present when focused

### List

#### 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.

#### Data Attributes

**`data-scope`**: combobox
**`data-part`**: list
**`data-empty`**: Present when the content is empty

### Positioner

#### 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.

### RootProvider

#### Props

**`value`**
Type: `UseComboboxReturn<T>`
Required: true
Default Value: `undefined`
Description: undefined

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

**`hideMode`**
Type: `HideMode`
Required: false
Default Value: `'display-none'`
Description: How to hide content when mounted but not present.
- `'display-none'`: HTML `hidden` attribute. Effects stay alive.
- `'activity'`: React 19 `<Activity mode="hidden">`. Effects pause. Requires React 19+.

**`immediate`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether to synchronize the present change immediately or defer it to the next frame

**`lazyMount`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether to enable lazy mounting

**`onExitComplete`**
Type: `VoidFunction`
Required: false
Default Value: `undefined`
Description: Function called when the animation ends in the closed state

**`present`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether the node is present (controlled by the user)

**`skipAnimationOnMount`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether to allow the initial presence animation.

**`unmountOnExit`**
Type: `boolean`
Required: false
Default Value: `false`
Description: Whether to unmount on exit.

### Trigger

#### 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.

**`focusable`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether the trigger is focusable

#### Data Attributes

**`data-scope`**: combobox
**`data-part`**: trigger
**`data-state`**: "open" | "closed"
**`data-invalid`**: Present when invalid
**`data-focusable`**: 
**`data-readonly`**: Present when read-only
**`data-disabled`**: Present when disabled

### Context

**API:**

| Property | Type | Description |
|----------|------|-------------|
| `focused` | `boolean` | Whether the combobox is focused |
| `open` | `boolean` | Whether the combobox is open |
| `inputValue` | `string` | The value of the combobox input |
| `highlightedValue` | `string | null` | The value of the highlighted item |
| `highlightedItem` | `V | null` | The highlighted item |
| `setHighlightValue` | `(value: string) => void` | The value of the combobox input |
| `clearHighlightValue` | `VoidFunction` | Function to clear the highlighted value |
| `syncSelectedItems` | `VoidFunction` | Function to sync the selected items with the value.
Useful when `value` is updated from async sources. |
| `selectedItems` | `V[]` | The selected items |
| `hasSelectedItems` | `boolean` | Whether there's a selected item |
| `value` | `string[]` | The selected item keys |
| `valueAsString` | `string` | The string representation of the selected items |
| `selectValue` | `(value: string) => void` | Function to select a value |
| `setValue` | `(value: string[]) => void` | Function to set the value of the combobox |
| `clearValue` | `(value?: string) => void` | Function to clear the value of the combobox |
| `focus` | `VoidFunction` | Function to focus on the combobox input |
| `setInputValue` | `(value: string, reason?: InputValueChangeReason) => void` | Function to set the input value of the combobox |
| `getItemState` | `(props: ItemProps) => ItemState` | Returns the state of a combobox item |
| `setOpen` | `(open: boolean, reason?: OpenChangeReason) => void` | Function to open or close the combobox |
| `collection` | `ListCollection<V>` | Function to toggle the combobox |
| `reposition` | `(options?: Partial<PositioningOptions>) => void` | Function to set the positioning options |
| `multiple` | `boolean` | Whether the combobox allows multiple selections |
| `disabled` | `boolean` | Whether the combobox is disabled |


## Accessibility

Complies with the [Combobox WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/).

### Keyboard Support

**`ArrowDown`**
Description: When the combobox is closed, opens the listbox and highlights to the first option.
When the combobox is open, moves focus to the next option.

**`ArrowUp`**
Description: When the combobox is closed, opens the listbox and highlights to the last option.
When the combobox is open, moves focus to the previous option.

**`Home`**
Description: When the combobox is open, moves focus to the first option.

**`End`**
Description: When the combobox is open, moves focus to the last option.

**`Escape`**
Description: Closes the listbox.

**`Enter`**
Description: Selects the highlighted option and closes the combobox.

**`Esc`**
Description: Closes the combobox