# Combobox URL: https://ark-ui.com/docs/components/combobox Source: https://raw.githubusercontent.com/chakra-ui/ark/refs/heads/main/website/src/content/pages/components/combobox.mdx A single input field that combines the functionality of a select and input. --- ## Anatomy ```tsx ``` ## Examples **Example: basic** #### React ```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 ( Favorite Fruit
{collection.items.map((item) => ( {item.label} ))}
) } ``` #### Solid ```tsx import { Combobox, useListCollection } from '@ark-ui/solid/combobox' import { useFilter } from '@ark-ui/solid/locale' import { CheckIcon, ChevronsUpDownIcon, XIcon } from 'lucide-solid' import { For } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/combobox.module.css' export const Basic = () => { const filterFn = 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: filterFn().contains, }) const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue) } return ( Favorite Fruit
{(item) => ( {item.label} )}
) } ``` #### Vue ```vue ``` #### Svelte ```svelte Fruit
Clear Open
No results found {#each collection().items as item (item.value)} {item.label} {/each}
``` ### Auto Highlight Automatically highlight the first matching item as the user types by setting `inputBehavior="autohighlight"`. **Example: auto-highlight** #### React ```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 ( Department
No results found {collection.items.map((item) => ( {item.label} ))}
) } ``` #### Solid ```tsx import { Combobox, useListCollection } from '@ark-ui/solid/combobox' import { useFilter } from '@ark-ui/solid/locale' import { For } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/combobox.module.css' export const AutoHighlight = () => { const filterFn = 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: filterFn().contains, }) const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue) } return ( Department
Clear Open
No results found {(item) => ( {item.label} )}
) } ``` #### Vue ```vue ``` #### Svelte ```svelte Department
Clear Open
No results found {#each collection().items as item (item.value)} {item.label} {/each}
``` ### Inline Autocomplete Complete the input value with the first matching item by setting `inputBehavior="autocomplete"`. Use with `startsWith` filter for best results. **Example: inline-autocomplete** #### React ```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 ( Sea Creature
No results found {collection.items.map((item) => ( {item.label} ))}
) } ``` #### Solid ```tsx import { Combobox, useListCollection } from '@ark-ui/solid/combobox' import { useFilter } from '@ark-ui/solid/locale' import { For } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/combobox.module.css' export const InlineAutocomplete = () => { const filterFn = 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: filterFn().startsWith, }) const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue) } return ( Sea Creature
Clear Open
No results found {(item) => ( {item.label} )}
) } ``` #### Vue ```vue ``` #### Svelte ```svelte Sea Creature
Clear Open
No results found {#each collection().items as item (item.value)} {item.label} {/each}
``` ### Grouping To group related combobox items, use the `groupBy` prop on the collection and `collection.group()` to iterate the groups. **Example: grouping** #### React ```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 ( Country
{collection.group().map(([continent, group]) => ( {continent} {group.map((item) => ( {item.label} ))} ))}
) } 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' }, ] ``` #### Solid ```tsx import { Combobox, useListCollection } from '@ark-ui/solid/combobox' import { useFilter } from '@ark-ui/solid/locale' import { CheckIcon, ChevronsUpDownIcon, XIcon } from 'lucide-solid' import { For } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/combobox.module.css' 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' }, ] export const Grouping = () => { const filterFn = useFilter({ sensitivity: 'base' }) const { collection, filter } = useListCollection({ initialItems, filter: filterFn().contains, groupBy: (item) => item.continent, }) const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue) } return ( Country
{([continent, group]) => ( {continent} {(item) => ( {item.label} )} )}
) } ``` #### Vue ```vue ``` #### Svelte ```svelte Country
Clear Open
{#each collection().group() as [continent, group]} {continent} {#each group as item} {item.label} {/each} {/each}
``` ### 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. **Example: with-field** #### React ```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 ( Department
{collection.items.map((item) => ( {item.label} ))}
Select your primary department Department is required
) } 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' }, ] ``` #### Solid ```tsx import { Combobox, useListCollection } from '@ark-ui/solid/combobox' import { Field } from '@ark-ui/solid/field' import { useFilter } from '@ark-ui/solid/locale' import { For } from 'solid-js' import styles from 'styles/combobox.module.css' import field from 'styles/field.module.css' 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' }, ] export const WithField = () => { const filterFn = useFilter({ sensitivity: 'base' }) const { collection, filter } = useListCollection({ initialItems, filter: filterFn().contains, }) const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue) } return ( Department
Clear Open
{(item) => ( {item.label} )}
Select your primary department Department is required
) } ``` #### Vue ```vue ``` #### Svelte ```svelte Department
Clear Open
{#each collection().items as item (item.value)} {item.label} {/each}
Select your primary department Department is required
``` ### Context Use the `Combobox.Context` component to access the combobox state and display information like the selected value. **Example: context** #### React ```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) =>

Selected: {combobox.valueAsString || 'None'}

}
Size
{collection.items.map((item) => ( {item.label} ))}
) } ``` #### Solid ```tsx import { Combobox, useListCollection } from '@ark-ui/solid/combobox' import { useFilter } from '@ark-ui/solid/locale' import { CheckIcon, ChevronsUpDownIcon, XIcon } from 'lucide-solid' import { For } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/combobox.module.css' export const Context = () => { const filterFn = 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: filterFn().contains, }) const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue) } return ( {(context) =>

Selected: {context().valueAsString || 'None'}

}
Size
{(item) => ( {item.label} )}
) } ``` #### Vue ```vue ``` #### Svelte ```svelte {#snippet render(combobox)}

Selected: {combobox().valueAsString || 'None'}

{/snippet}
Size
Clear Open
{#each collection().items as item (item.value)} {item.label} {/each}
``` ### 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. **Example: root-provider** #### React ```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 (
Job Title
{collection.items.map((item) => ( {item.label} ))}
) } ``` #### Solid ```tsx import { Combobox, useCombobox, useListCollection } from '@ark-ui/solid/combobox' import { useFilter } from '@ark-ui/solid/locale' import { CheckIcon, ChevronsUpDownIcon, XIcon } from 'lucide-solid' import { For } from 'solid-js' import { Portal } from 'solid-js/web' 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 filterFn = useFilter({ sensitivity: 'base' }) const { collection, filter } = useListCollection({ initialItems, filter: filterFn().contains, }) const combobox = useCombobox({ get collection() { return collection() }, onInputValueChange(details) { filter(details.inputValue) }, }) return (
Job Title
{(item) => ( {item.label} )}
) } ``` #### Vue ```vue ``` #### Svelte ```svelte
Job Title
Clear Open
{#each collection().items as item (item.value)} {item.label} {/each}
``` ### Links Use the `asChild` prop to render the combobox items as links. **Example: links** #### React ```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 ( Developer Resources
{collection.items.map((item) => ( {item.label} ))}
) } 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' }, ] ``` #### Solid ```tsx import { Combobox, useListCollection } from '@ark-ui/solid/combobox' import { useFilter } from '@ark-ui/solid/locale' import { CheckIcon, ChevronsUpDownIcon } from 'lucide-solid' import { For } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/combobox.module.css' 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' }, ] export const Links = () => { const filterFn = useFilter({ sensitivity: 'base' }) const { collection, filter } = useListCollection({ initialItems, filter: filterFn().contains, }) const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue) } return ( Developer Resources
{(item) => ( }> {item.label} )}
) } ``` #### Vue ```vue ``` #### Svelte ```svelte Developer Resources
Open
{#each collection().items as item (item.value)} {#snippet asChild(props)}
{item.label} {/snippet} {/each} ``` ### 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. **Example: rehydrate-value** #### React ```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({ 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 ( Search Star Wars Characters {state.loading ? ( Loading... ) : state.error ? ( {state.error.message} ) : ( collection.items.map((item) => ( {item.name} - {item.height}cm / {item.mass}kg )) )} ) } interface Character { name: string height: string mass: string created: string edited: string url: string } ``` #### Solid ```tsx import { Combobox, useCombobox, useComboboxContext, useListCollection } from '@ark-ui/solid/combobox' import { For, createEffect, createRenderEffect, createSignal, on } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/combobox.module.css' import { useAsync } from './use-async' function ComboboxRehydrateValue() { const combobox = useComboboxContext() let hydrated = false createRenderEffect(() => { if (combobox().value.length && combobox().collection.size && !hydrated) { combobox().syncSelectedItems() hydrated = true } }) return null } export const RehydrateValue = () => { const [inputValue, setInputValue] = createSignal('') const { collection, set } = useListCollection({ initialItems: [], itemToString: (item) => item.name, itemToValue: (item) => item.name, }) const combobox = useCombobox(() => ({ collection: collection(), defaultValue: ['C-3PO'], placeholder: 'Example: Dexter', inputValue: inputValue(), onInputValueChange: (e) => setInputValue(e.inputValue), })) const state = useAsync(async (signal) => { const response = await fetch(`https://swapi.py4e.com/api/people/?search=${inputValue()}`, { signal }) const data = await response.json() set(data.results) }) createEffect(on(inputValue, () => state.load())) return ( Search Star Wars Characters {state.loading() ? ( Loading... ) : state.error() ? ( {state.error()?.message} ) : ( {(item) => ( {item.name} - {item.height}cm / {item.mass}kg )} )} ) } interface Character { name: string height: string mass: string created: string edited: string url: string } ``` #### Vue ```vue ``` #### Svelte ```svelte Search Star Wars Characters {#if _state.loading()} Loading... {:else if _state.error()} {_state.error()?.message} {:else} {#each collection().items as item (item.name)} {item.name} - {item.height}cm / {item.mass}kg {/each} {/if} ``` ### Highlight Text Highlight the matching search text in combobox items based on the user's input. **Example: highlight-matching-text** #### React ```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 ( Assignee
{collection.items.map((item) => ( {(context) => } ))}
) } ``` #### Solid ```tsx import { Combobox, useListCollection } from '@ark-ui/solid/combobox' import { Highlight } from '@ark-ui/solid/highlight' import { useFilter } from '@ark-ui/solid/locale' import { For } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/combobox.module.css' export const HighlightMatchingText = () => { const filterFn = 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: filterFn().contains, }) const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue) } return ( Assignee
Clear Open
{(item) => ( {(context) => } )}
) } ``` #### Vue ```vue ``` #### Svelte ```svelte Assignee
Clear Open
{#each collection().items as item (item.value)} {#snippet render(context)} {/snippet} {/each}
``` ### Dynamic Generate combobox items dynamically based on user input. This is useful for creating suggestions or autocomplete functionality. **Example: dynamic** #### React ```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({ initialItems: [], }) const handleInputChange = (details: Combobox.InputValueChangeDetails) => { if (details.reason === 'input-change') { const items = suggestList.map((item) => `${details.inputValue}@${item}`) set(items) } } return ( Email
{collection.items.map((item) => ( {item} ))}
) } ``` #### Solid ```tsx import { Combobox, useListCollection } from '@ark-ui/solid/combobox' import { For } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/combobox.module.css' const suggestList = ['gmail.com', 'yahoo.com', 'ark-ui.com'] export const Dynamic = () => { const { collection, set } = useListCollection({ initialItems: [], }) const handleInputChange = (details: Combobox.InputValueChangeDetails) => { if (details.reason === 'input-change') { const items = suggestList.map((item) => `${details.inputValue}@${item}`) set(items) } } return ( Email
Open
{(item) => ( {item} )}
) } ``` #### Vue ```vue ``` #### Svelte ```svelte Email
Open
{#each collection().items as item (item)} {item} {/each}
``` ### 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. **Example: creatable** #### React ```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({ 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([]) 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 ( Label
{collection.items.map((item) => ( {isNewOptionValue(item.value) ? ( + Create "{item.label}" ) : ( {item.label} {item.__new__ ? '(new)' : ''} )} ))}
) } ``` #### Solid ```tsx import { Combobox, useListCollection } from '@ark-ui/solid/combobox' import { useFilter } from '@ark-ui/solid/locale' import { createSignal, For } from 'solid-js' import { Portal } from 'solid-js/web' 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 filterFn = useFilter({ sensitivity: 'base' }) const { collection, filter, upsert, update, remove } = useListCollection({ initialItems: [ { label: 'Bug', value: 'bug' }, { label: 'Feature', value: 'feature' }, { label: 'Enhancement', value: 'enhancement' }, { label: 'Documentation', value: 'docs' }, ], filter: filterFn().contains, }) const isValidNewOption = (inputValue: string) => { const exactOptionMatch = collection().items.filter((item) => item.label.toLowerCase() === inputValue.toLowerCase()).length > 0 return !exactOptionMatch && inputValue.trim().length > 0 } const [selectedValue, setSelectedValue] = createSignal([]) const [inputValue, setInputValue] = createSignal('') const handleInputChange = ({ inputValue: newInputValue, reason }: Combobox.InputValueChangeDetails) => { if (reason === 'input-change' || reason === 'item-select') { if (isValidNewOption(newInputValue)) { upsert(NEW_OPTION_VALUE, createNewOption(newInputValue)) } else if (newInputValue.trim().length === 0) { remove(NEW_OPTION_VALUE) } filter(newInputValue) } setInputValue(newInputValue) } 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 ( Label
Clear Open
{(item) => ( {isNewOptionValue(item.value) ? ( + Create "{item.label}" ) : ( {item.label} {item.__new__ ? '(new)' : ''} )} )}
) } ``` #### Vue ```vue ``` #### Svelte ```svelte Label
Clear Open
{#each collection().items as item (item.value)} {#if isNewOptionValue(item.value)} + Create "{item.label}" {:else} {item.label} {item.__new__ ? '(new)' : ''} {/if} {/each}
``` ### Multiple Selection Enable multiple selection by setting the `multiple` prop. Selected items can be displayed as tags above the input. **Example: multiple** #### React ```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([]) 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 ( Skills {(combobox) => (
{combobox.selectedItems.length === 0 && None selected} {combobox.selectedItems.map((item: any) => ( {item.label} ))}
)}
No skills found {collection.items.map((item) => ( {item.label} ))}
) } ``` #### Solid ```tsx import { Combobox, useListCollection } from '@ark-ui/solid/combobox' import { useFilter } from '@ark-ui/solid/locale' import { CheckIcon, ChevronsUpDownIcon } from 'lucide-solid' import { For } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/combobox.module.css' export const Multiple = () => { const filterFn = useFilter({ sensitivity: 'base' }) 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: filterFn().contains, }) const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue) } const handleValueChange = (details: Combobox.ValueChangeDetails) => { remove(...details.value) } return ( Skills {(context) => (
{context().selectedItems.length === 0 && None selected} {(item: any) => {item.label}}
)}
No skills found {(item) => ( {item.label} )}
) } ``` #### Vue ```vue ``` #### Svelte ```svelte Skills
{#if selectedItems.length === 0} None selected {/if} {#each selectedItems as item (item.value)} {item.label} {/each}
Open
No skills found {#each collection().items as item (item.value)} {item.label} {/each}
``` ### 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. **Example: async-search** #### React ```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({ 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 ( Movie
{list.loading ? (
Searching...
) : list.error ? (
{list.error.message}
) : list.items.length === 0 ? (
{list.filterText ? 'No results found' : 'Start typing to search movies...'}
) : ( collection.items.map((movie) => ( {movie.title} {movie.year} · {movie.director} )) )}
) } 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. **Example: virtualized** #### React ```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(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['scrollToIndexFn'] = (details) => { flushSync(() => { virtualizer.scrollToIndex(details.index, { align: 'center', behavior: 'auto', }) }) } const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue) } return ( Country
{virtualizer.getVirtualItems().map((virtualItem) => { const item = collection.items[virtualItem.index] return ( {item.emoji} {item.label} ) })}
) } 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: '🇿🇼' }, ] ``` #### Solid ```tsx import { Combobox, useListCollection } from '@ark-ui/solid/combobox' import { useFilter } from '@ark-ui/solid/locale' import { createVirtualizer } from '@tanstack/solid-virtual' import { CheckIcon, ChevronsUpDownIcon } from 'lucide-solid' import { Portal } from 'solid-js/web' import styles from 'styles/combobox.module.css' export const Virtualized = () => { let contentRef: HTMLDivElement | undefined const filterFn = useFilter({ sensitivity: 'base' }) const { collection, filter, reset } = useListCollection({ initialItems: countries, filter: filterFn().startsWith, }) const virtualizer = createVirtualizer({ get count() { return collection().size }, getScrollElement: () => contentRef ?? null, estimateSize: () => 32, overscan: 10, }) const handleScrollToIndex: Combobox.RootProps['scrollToIndexFn'] = (details) => { virtualizer.scrollToIndex(details.index, { align: 'center', behavior: 'auto', }) } const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue) } return ( Country
{virtualizer.getVirtualItems().map((virtualItem) => { const item = collection().items[virtualItem.index] return ( {item.emoji} {item.label} ) })}
) } 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: '🇿🇼' }, ] ``` #### Vue ```vue ``` #### Svelte ```svelte Country
Open
{#each $virtualizer.getVirtualItems() as virtualItem (virtualItem.key)} {@const item = collection().items[virtualItem.index]} {item.label} {/each}
``` ### Custom Object Use the `itemToString` and `itemToValue` props to map custom objects to the required interface. **Example: custom-object** #### React ```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 ( Country
{collection.items.map((item) => ( {item.flag} {item.country} ))}
) } ``` #### Solid ```tsx import { Combobox, useListCollection } from '@ark-ui/solid/combobox' import { useFilter } from '@ark-ui/solid/locale' import { For } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/combobox.module.css' export const CustomObject = () => { const filterFn = 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: filterFn().contains, }) const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue) } return ( Country
Clear Open
{(item) => ( {item.flag} {item.country} )}
) } ``` #### Vue ```vue ``` #### Svelte ```svelte Country
Clear Open
{#each collection().items as item (item.code)} {item.flag} {item.country} {/each}
``` ### Limit Results Use the `limit` property on `useListCollection` to limit the number of rendered items in the DOM. **Example: limit-results** #### React ```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 ( City
{collection.items.map((item) => ( {item.label} ))}
) } ``` #### Solid ```tsx import { Combobox, useListCollection } from '@ark-ui/solid/combobox' import { useFilter } from '@ark-ui/solid/locale' import { For } from 'solid-js' import { Portal } from 'solid-js/web' 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 filterFn = useFilter({ sensitivity: 'base' }) const { collection, filter } = useListCollection({ initialItems: cities, limit: 5, filter: filterFn().contains, }) const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue) } return ( City
Open
{(item) => ( {item.label} )}
) } ``` #### Vue ```vue ``` #### Svelte ```svelte City
Open
{#each collection().items as item (item.value)} {item.label} {/each}
``` ## Guides ### Router Links Customize the `navigate` prop on `Combobox.Root` to integrate with your router. Using Tanstack Router: ```tsx import { Combobox } from '@ark-ui//combobox' import { useNavigate } from '@tanstack/react-router' function Demo() { const navigate = useNavigate() return ( { navigate({ to: e.node.href }) }} > {/* ... */} ) } ``` ### 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 {/* ... */} } ``` 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 ( { // e.items is typed as Array<{ label: string, value: string }> console.log(e.items) }} > {/* ... */} ) } ``` ### 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: ; /* width of the available viewport */ --available-width: ; /* height of the available viewport */ --available-height: ; ``` 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 **Component API Reference** #### React **Root Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `collection` | `ListCollection` | Yes | The collection of items | | `allowCustomValue` | `boolean` | No | Whether to allow typing custom values in the input | | `alwaysSubmitOnEnter` | `boolean` | No | 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` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `autoFocus` | `boolean` | No | Whether to autofocus the input on mount | | `closeOnSelect` | `boolean` | No | Whether to close the combobox when an item is selected. | | `composite` | `boolean` | No | Whether the combobox is a composed with other composite widgets like tabs | | `defaultHighlightedValue` | `string` | No | The initial highlighted value of the combobox when rendered. Use when you don't need to control the highlighted value of the combobox. | | `defaultInputValue` | `string` | No | 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` | `boolean` | No | The initial open state of the combobox when rendered. Use when you don't need to control the open state of the combobox. | | `defaultValue` | `string[]` | No | 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` | `boolean` | No | Whether the combobox is disabled | | `disableLayer` | `boolean` | No | Whether to disable registering this a dismissable layer | | `form` | `string` | No | The associate form of the combobox. | | `highlightedValue` | `string` | No | The controlled highlighted value of the combobox | | `id` | `string` | No | The unique identifier of the machine. | | `ids` | `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 }>` | No | The ids of the elements in the combobox. Useful for composition. | | `immediate` | `boolean` | No | Whether to synchronize the present change immediately or defer it to the next frame | | `inputBehavior` | `'none' | 'autohighlight' | 'autocomplete'` | No | 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` | `string` | No | The controlled value of the combobox's input | | `invalid` | `boolean` | No | Whether the combobox is invalid | | `lazyMount` | `boolean` | No | Whether to enable lazy mounting | | `loopFocus` | `boolean` | No | Whether to loop the keyboard navigation through the items | | `multiple` | `boolean` | No | 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` | `string` | No | The `name` attribute of the combobox's input. Useful for form submission | | `navigate` | `(details: NavigateDetails) => void` | No | Function to navigate to the selected item | | `onExitComplete` | `VoidFunction` | No | Function called when the animation ends in the closed state | | `onFocusOutside` | `(event: FocusOutsideEvent) => void` | No | Function called when the focus is moved outside the component | | `onHighlightChange` | `(details: HighlightChangeDetails) => void` | No | Function called when an item is highlighted using the pointer or keyboard navigation. | | `onInputValueChange` | `(details: InputValueChangeDetails) => void` | No | Function called when the input's value changes | | `onInteractOutside` | `(event: InteractOutsideEvent) => void` | No | Function called when an interaction happens outside the component | | `onOpenChange` | `(details: OpenChangeDetails) => void` | No | Function called when the popup is opened | | `onPointerDownOutside` | `(event: PointerDownOutsideEvent) => void` | No | Function called when the pointer is pressed down outside the component | | `onSelect` | `(details: SelectionDetails) => void` | No | Function called when an item is selected | | `onValueChange` | `(details: ValueChangeDetails) => void` | No | Function called when a new item is selected | | `open` | `boolean` | No | The controlled open state of the combobox | | `openOnChange` | `boolean | ((details: InputValueChangeDetails) => boolean)` | No | Whether to show the combobox when the input value changes | | `openOnClick` | `boolean` | No | Whether to open the combobox popup on initial click on the input | | `openOnKeyPress` | `boolean` | No | Whether to open the combobox on arrow key press | | `placeholder` | `string` | No | The placeholder text of the combobox's input | | `positioning` | `PositioningOptions` | No | The positioning options to dynamically position the menu | | `present` | `boolean` | No | Whether the node is present (controlled by the user) | | `readOnly` | `boolean` | No | Whether the combobox is readonly. This puts the combobox in a "non-editable" mode but the user can still interact with it | | `required` | `boolean` | No | Whether the combobox is required | | `scrollToIndexFn` | `(details: ScrollToIndexDetails) => void` | No | Function to scroll to a specific index | | `selectionBehavior` | `'clear' | 'replace' | 'preserve'` | No | 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` | `boolean` | No | Whether to allow the initial presence animation. | | `translations` | `IntlTranslations` | No | Specifies the localized strings that identifies the accessibility elements and their states | | `unmountOnExit` | `boolean` | No | Whether to unmount on exit. | | `value` | `string[]` | No | The controlled value of the combobox's selected items | **Root Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | root | | `[data-invalid]` | Present when invalid | | `[data-readonly]` | Present when read-only | **ClearTrigger Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **ClearTrigger Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | clear-trigger | | `[data-invalid]` | Present when invalid | **Content Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Content Data Attributes:** | Attribute | Value | |-----------|-------| | `[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-empty]` | Present when the content is empty | **Content CSS Variables:** | Variable | Description | |----------|-------------| | `--layer-index` | The index of the dismissable in the layer stack | | `--nested-layer-count` | The number of nested comboboxs | **Control Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Control Data Attributes:** | Attribute | Value | |-----------|-------| | `[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:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Input Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Input Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | input | | `[data-invalid]` | Present when invalid | | `[data-autofocus]` | | | `[data-state]` | "open" | "closed" | **ItemGroupLabel Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **ItemGroup Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **ItemGroup Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | item-group | | `[data-empty]` | Present when the content is empty | **ItemIndicator Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **ItemIndicator Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | item-indicator | | `[data-state]` | "checked" | "unchecked" | **Item Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `item` | `any` | No | The item to render | | `persistFocus` | `boolean` | No | Whether hovering outside should clear the highlighted state | **Item Data Attributes:** | Attribute | Value | |-----------|-------| | `[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:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **ItemText Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | item-text | | `[data-state]` | "checked" | "unchecked" | | `[data-disabled]` | Present when disabled | | `[data-highlighted]` | Present when highlighted | **Label Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Label Data Attributes:** | Attribute | Value | |-----------|-------| | `[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:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **List Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | list | | `[data-empty]` | Present when the content is empty | **Positioner Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Positioner CSS Variables:** | Variable | Description | |----------|-------------| | `--reference-width` | The width of the reference element | | `--reference-height` | The height of the root | | `--available-width` | The available width in viewport | | `--available-height` | The available height in viewport | | `--x` | The x position for transform | | `--y` | The y position for transform | | `--z-index` | The z-index value | | `--transform-origin` | The transform origin for animations | **RootProvider Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `value` | `UseComboboxReturn` | Yes | | | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `immediate` | `boolean` | No | Whether to synchronize the present change immediately or defer it to the next frame | | `lazyMount` | `boolean` | No | Whether to enable lazy mounting | | `onExitComplete` | `VoidFunction` | No | Function called when the animation ends in the closed state | | `present` | `boolean` | No | Whether the node is present (controlled by the user) | | `skipAnimationOnMount` | `boolean` | No | Whether to allow the initial presence animation. | | `unmountOnExit` | `boolean` | No | Whether to unmount on exit. | **Trigger Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `focusable` | `boolean` | No | Whether the trigger is focusable | **Trigger Data Attributes:** | Attribute | Value | |-----------|-------| | `[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 | #### Solid **Root Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `collection` | `ListCollection` | Yes | The collection of items | | `allowCustomValue` | `boolean` | No | Whether to allow typing custom values in the input | | `alwaysSubmitOnEnter` | `boolean` | No | 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` | `(props: ParentProps<'div'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `autoFocus` | `boolean` | No | Whether to autofocus the input on mount | | `closeOnSelect` | `boolean` | No | Whether to close the combobox when an item is selected. | | `composite` | `boolean` | No | Whether the combobox is a composed with other composite widgets like tabs | | `defaultHighlightedValue` | `string` | No | The initial highlighted value of the combobox when rendered. Use when you don't need to control the highlighted value of the combobox. | | `defaultInputValue` | `string` | No | 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` | `boolean` | No | The initial open state of the combobox when rendered. Use when you don't need to control the open state of the combobox. | | `defaultValue` | `string[]` | No | 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` | `boolean` | No | Whether the combobox is disabled | | `disableLayer` | `boolean` | No | Whether to disable registering this a dismissable layer | | `form` | `string` | No | The associate form of the combobox. | | `highlightedValue` | `string` | No | The controlled highlighted value of the combobox | | `ids` | `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 }>` | No | The ids of the elements in the combobox. Useful for composition. | | `immediate` | `boolean` | No | Whether to synchronize the present change immediately or defer it to the next frame | | `inputBehavior` | `'none' | 'autohighlight' | 'autocomplete'` | No | 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` | `string` | No | The controlled value of the combobox's input | | `invalid` | `boolean` | No | Whether the combobox is invalid | | `lazyMount` | `boolean` | No | Whether to enable lazy mounting | | `loopFocus` | `boolean` | No | Whether to loop the keyboard navigation through the items | | `multiple` | `boolean` | No | 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` | `string` | No | The `name` attribute of the combobox's input. Useful for form submission | | `navigate` | `(details: NavigateDetails) => void` | No | Function to navigate to the selected item | | `onExitComplete` | `VoidFunction` | No | Function called when the animation ends in the closed state | | `onFocusOutside` | `(event: FocusOutsideEvent) => void` | No | Function called when the focus is moved outside the component | | `onHighlightChange` | `(details: HighlightChangeDetails) => void` | No | Function called when an item is highlighted using the pointer or keyboard navigation. | | `onInputValueChange` | `(details: InputValueChangeDetails) => void` | No | Function called when the input's value changes | | `onInteractOutside` | `(event: InteractOutsideEvent) => void` | No | Function called when an interaction happens outside the component | | `onOpenChange` | `(details: OpenChangeDetails) => void` | No | Function called when the popup is opened | | `onPointerDownOutside` | `(event: PointerDownOutsideEvent) => void` | No | Function called when the pointer is pressed down outside the component | | `onSelect` | `(details: SelectionDetails) => void` | No | Function called when an item is selected | | `onValueChange` | `(details: ValueChangeDetails) => void` | No | Function called when a new item is selected | | `open` | `boolean` | No | The controlled open state of the combobox | | `openOnChange` | `boolean | ((details: InputValueChangeDetails) => boolean)` | No | Whether to show the combobox when the input value changes | | `openOnClick` | `boolean` | No | Whether to open the combobox popup on initial click on the input | | `openOnKeyPress` | `boolean` | No | Whether to open the combobox on arrow key press | | `placeholder` | `string` | No | The placeholder text of the combobox's input | | `positioning` | `PositioningOptions` | No | The positioning options to dynamically position the menu | | `present` | `boolean` | No | Whether the node is present (controlled by the user) | | `readOnly` | `boolean` | No | Whether the combobox is readonly. This puts the combobox in a "non-editable" mode but the user can still interact with it | | `required` | `boolean` | No | Whether the combobox is required | | `scrollToIndexFn` | `(details: ScrollToIndexDetails) => void` | No | Function to scroll to a specific index | | `selectionBehavior` | `'replace' | 'clear' | 'preserve'` | No | 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` | `boolean` | No | Whether to allow the initial presence animation. | | `translations` | `IntlTranslations` | No | Specifies the localized strings that identifies the accessibility elements and their states | | `unmountOnExit` | `boolean` | No | Whether to unmount on exit. | | `value` | `string[]` | No | The controlled value of the combobox's selected items | **Root Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | root | | `[data-invalid]` | Present when invalid | | `[data-readonly]` | Present when read-only | **ClearTrigger Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `(props: ParentProps<'button'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **ClearTrigger Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | clear-trigger | | `[data-invalid]` | Present when invalid | **Content Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `(props: ParentProps<'div'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Content Data Attributes:** | Attribute | Value | |-----------|-------| | `[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-empty]` | Present when the content is empty | **Content CSS Variables:** | Variable | Description | |----------|-------------| | `--layer-index` | The index of the dismissable in the layer stack | | `--nested-layer-count` | The number of nested comboboxs | **Control Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `(props: ParentProps<'div'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Control Data Attributes:** | Attribute | Value | |-----------|-------| | `[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:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `(props: ParentProps<'div'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Input Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `(props: ParentProps<'input'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Input Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | input | | `[data-invalid]` | Present when invalid | | `[data-autofocus]` | | | `[data-state]` | "open" | "closed" | **ItemGroupLabel Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `(props: ParentProps<'div'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **ItemGroup Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `(props: ParentProps<'div'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **ItemGroup Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | item-group | | `[data-empty]` | Present when the content is empty | **ItemIndicator Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `(props: ParentProps<'div'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **ItemIndicator Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | item-indicator | | `[data-state]` | "checked" | "unchecked" | **Item Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `(props: ParentProps<'div'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `item` | `any` | No | The item to render | | `persistFocus` | `boolean` | No | Whether hovering outside should clear the highlighted state | **Item Data Attributes:** | Attribute | Value | |-----------|-------| | `[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:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `(props: ParentProps<'span'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **ItemText Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | item-text | | `[data-state]` | "checked" | "unchecked" | | `[data-disabled]` | Present when disabled | | `[data-highlighted]` | Present when highlighted | **Label Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `(props: ParentProps<'label'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Label Data Attributes:** | Attribute | Value | |-----------|-------| | `[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:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `(props: ParentProps<'div'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **List Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | list | | `[data-empty]` | Present when the content is empty | **Positioner Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `(props: ParentProps<'div'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Positioner CSS Variables:** | Variable | Description | |----------|-------------| | `--reference-width` | The width of the reference element | | `--reference-height` | The height of the root | | `--available-width` | The available width in viewport | | `--available-height` | The available height in viewport | | `--x` | The x position for transform | | `--y` | The y position for transform | | `--z-index` | The z-index value | | `--transform-origin` | The transform origin for animations | **RootProvider Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `value` | `UseComboboxReturn` | Yes | | | `asChild` | `(props: ParentProps<'div'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `immediate` | `boolean` | No | Whether to synchronize the present change immediately or defer it to the next frame | | `lazyMount` | `boolean` | No | Whether to enable lazy mounting | | `onExitComplete` | `VoidFunction` | No | Function called when the animation ends in the closed state | | `present` | `boolean` | No | Whether the node is present (controlled by the user) | | `skipAnimationOnMount` | `boolean` | No | Whether to allow the initial presence animation. | | `unmountOnExit` | `boolean` | No | Whether to unmount on exit. | **Trigger Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `(props: ParentProps<'button'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `focusable` | `boolean` | No | Whether the trigger is focusable | **Trigger Data Attributes:** | Attribute | Value | |-----------|-------| | `[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 | #### Vue **Root Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `allowCustomValue` | `boolean` | No | Whether to allow typing custom values in the input | | `alwaysSubmitOnEnter` | `boolean` | No | Whether to allow bypassing the default two-step behavior (Enter to close combobox, then Enter to submit form) and instead submit the form immediately on Enter press. This is useful for single-field autocomplete forms where Enter should submit the form directly. | | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `autoFocus` | `boolean` | No | Whether to autofocus the input on mount | | `closeOnSelect` | `boolean` | No | Whether to close the combobox when an item is selected. | | `collection` | `ListCollection` | No | The collection of items | | `composite` | `boolean` | No | Whether the combobox is a composed with other composite widgets like tabs | | `defaultHighlightedValue` | `string` | No | The initial highlighted value of the combobox when rendered. Use when you don't need to control the highlighted value of the combobox. | | `defaultInputValue` | `string` | No | 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` | `boolean` | No | The initial open state of the combobox when rendered. Use when you don't need to control the open state of the combobox. | | `defaultValue` | `string[]` | No | 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` | `boolean` | No | Whether the combobox is disabled | | `disableLayer` | `boolean` | No | Whether to disable registering this a dismissable layer | | `form` | `string` | No | The associate form of the combobox. | | `highlightedValue` | `string` | No | The controlled highlighted value of the combobox | | `id` | `string` | No | The unique identifier of the machine. | | `ids` | `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 }>` | No | The ids of the elements in the combobox. Useful for composition. | | `inputBehavior` | `'none' | 'autohighlight' | 'autocomplete'` | No | 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` | `string` | No | The controlled value of the combobox's input | | `invalid` | `boolean` | No | Whether the combobox is invalid | | `lazyMount` | `boolean` | No | Whether to enable lazy mounting | | `loopFocus` | `boolean` | No | Whether to loop the keyboard navigation through the items | | `modelValue` | `string[]` | No | The v-model value of the combobox | | `multiple` | `boolean` | No | 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` | `string` | No | The `name` attribute of the combobox's input. Useful for form submission | | `navigate` | `(details: NavigateDetails) => void` | No | Function to navigate to the selected item | | `open` | `boolean` | No | The controlled open state of the combobox | | `openOnChange` | `boolean | ((details: InputValueChangeDetails) => boolean)` | No | Whether to show the combobox when the input value changes | | `openOnClick` | `boolean` | No | Whether to open the combobox popup on initial click on the input | | `openOnKeyPress` | `boolean` | No | Whether to open the combobox on arrow key press | | `placeholder` | `string` | No | The placeholder text of the combobox's input | | `positioning` | `PositioningOptions` | No | The positioning options to dynamically position the menu | | `readOnly` | `boolean` | No | Whether the combobox is readonly. This puts the combobox in a "non-editable" mode but the user can still interact with it | | `required` | `boolean` | No | Whether the combobox is required | | `scrollToIndexFn` | `(details: ScrollToIndexDetails) => void` | No | Function to scroll to a specific index | | `selectionBehavior` | `'clear' | 'replace' | 'preserve'` | No | 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 | | `translations` | `IntlTranslations` | No | Specifies the localized strings that identifies the accessibility elements and their states | | `unmountOnExit` | `boolean` | No | Whether to unmount on exit. | **Root Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | root | | `[data-invalid]` | Present when invalid | | `[data-readonly]` | Present when read-only | **ClearTrigger Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **ClearTrigger Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | clear-trigger | | `[data-invalid]` | Present when invalid | **Content Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Content Data Attributes:** | Attribute | Value | |-----------|-------| | `[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-empty]` | Present when the content is empty | **Content CSS Variables:** | Variable | Description | |----------|-------------| | `--layer-index` | The index of the dismissable in the layer stack | | `--nested-layer-count` | The number of nested comboboxs | **Control Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Control Data Attributes:** | Attribute | Value | |-----------|-------| | `[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:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Input Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Input Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | input | | `[data-invalid]` | Present when invalid | | `[data-autofocus]` | | | `[data-state]` | "open" | "closed" | **ItemGroupLabel Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **ItemGroup Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `id` | `string` | No | | **ItemGroup Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | item-group | | `[data-empty]` | Present when the content is empty | **ItemIndicator Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **ItemIndicator Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | item-indicator | | `[data-state]` | "checked" | "unchecked" | **Item Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `item` | `any` | No | The item to render | | `persistFocus` | `boolean` | No | Whether hovering outside should clear the highlighted state | **Item Data Attributes:** | Attribute | Value | |-----------|-------| | `[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:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **ItemText Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | item-text | | `[data-state]` | "checked" | "unchecked" | | `[data-disabled]` | Present when disabled | | `[data-highlighted]` | Present when highlighted | **Label Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Label Data Attributes:** | Attribute | Value | |-----------|-------| | `[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:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **List Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | list | | `[data-empty]` | Present when the content is empty | **Positioner Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | **Positioner CSS Variables:** | Variable | Description | |----------|-------------| | `--reference-width` | The width of the reference element | | `--reference-height` | The height of the root | | `--available-width` | The available width in viewport | | `--available-height` | The available height in viewport | | `--x` | The x position for transform | | `--y` | The y position for transform | | `--z-index` | The z-index value | | `--transform-origin` | The transform origin for animations | **RootProvider Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `value` | `ComboboxApi` | Yes | | | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `lazyMount` | `boolean` | No | Whether to enable lazy mounting | | `unmountOnExit` | `boolean` | No | Whether to unmount on exit. | **Trigger Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `focusable` | `boolean` | No | Whether the trigger is focusable | **Trigger Data Attributes:** | Attribute | Value | |-----------|-------| | `[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 | #### Svelte **Root Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `collection` | `MaybeFunction>` | Yes | The collection of items | | `allowCustomValue` | `boolean` | No | Whether to allow typing custom values in the input | | `alwaysSubmitOnEnter` | `boolean` | No | 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` | `Snippet<[PropsFn<'div'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `autoFocus` | `boolean` | No | Whether to autofocus the input on mount | | `closeOnSelect` | `boolean` | No | Whether to close the combobox when an item is selected. | | `composite` | `boolean` | No | Whether the combobox is a composed with other composite widgets like tabs | | `defaultHighlightedValue` | `string` | No | The initial highlighted value of the combobox when rendered. Use when you don't need to control the highlighted value of the combobox. | | `defaultInputValue` | `string` | No | 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` | `boolean` | No | The initial open state of the combobox when rendered. Use when you don't need to control the open state of the combobox. | | `defaultValue` | `string[]` | No | 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` | `boolean` | No | Whether the combobox is disabled | | `disableLayer` | `boolean` | No | Whether to disable registering this a dismissable layer | | `form` | `string` | No | The associate form of the combobox. | | `highlightedValue` | `string` | No | The controlled highlighted value of the combobox | | `id` | `string` | No | The unique identifier of the machine. | | `ids` | `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 }>` | No | The ids of the elements in the combobox. Useful for composition. | | `immediate` | `boolean` | No | Whether to synchronize the present change immediately or defer it to the next frame | | `inputBehavior` | `'none' | 'autocomplete' | 'autohighlight'` | No | 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` | `string` | No | The controlled value of the combobox's input | | `invalid` | `boolean` | No | Whether the combobox is invalid | | `lazyMount` | `boolean` | No | Whether to enable lazy mounting | | `loopFocus` | `boolean` | No | Whether to loop the keyboard navigation through the items | | `multiple` | `boolean` | No | 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` | `string` | No | The `name` attribute of the combobox's input. Useful for form submission | | `navigate` | `(details: NavigateDetails) => void` | No | Function to navigate to the selected item | | `onExitComplete` | `VoidFunction` | No | Function called when the animation ends in the closed state | | `onFocusOutside` | `(event: FocusOutsideEvent) => void` | No | Function called when the focus is moved outside the component | | `onHighlightChange` | `(details: HighlightChangeDetails) => void` | No | Function called when an item is highlighted using the pointer or keyboard navigation. | | `onInputValueChange` | `(details: InputValueChangeDetails) => void` | No | Function called when the input's value changes | | `onInteractOutside` | `(event: InteractOutsideEvent) => void` | No | Function called when an interaction happens outside the component | | `onOpenChange` | `(details: OpenChangeDetails) => void` | No | Function called when the popup is opened | | `onPointerDownOutside` | `(event: PointerDownOutsideEvent) => void` | No | Function called when the pointer is pressed down outside the component | | `onSelect` | `(details: SelectionDetails) => void` | No | Function called when an item is selected | | `onValueChange` | `(details: ValueChangeDetails) => void` | No | Function called when a new item is selected | | `open` | `boolean` | No | The controlled open state of the combobox | | `openOnChange` | `boolean | ((details: InputValueChangeDetails) => boolean)` | No | Whether to show the combobox when the input value changes | | `openOnClick` | `boolean` | No | Whether to open the combobox popup on initial click on the input | | `openOnKeyPress` | `boolean` | No | Whether to open the combobox on arrow key press | | `placeholder` | `string` | No | The placeholder text of the combobox's input | | `positioning` | `PositioningOptions` | No | The positioning options to dynamically position the menu | | `present` | `boolean` | No | Whether the node is present (controlled by the user) | | `readOnly` | `boolean` | No | Whether the combobox is readonly. This puts the combobox in a "non-editable" mode but the user can still interact with it | | `ref` | `Element` | No | | | `required` | `boolean` | No | Whether the combobox is required | | `scrollToIndexFn` | `(details: ScrollToIndexDetails) => void` | No | Function to scroll to a specific index | | `selectionBehavior` | `'replace' | 'clear' | 'preserve'` | No | 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` | `boolean` | No | Whether to allow the initial presence animation. | | `translations` | `IntlTranslations` | No | Specifies the localized strings that identifies the accessibility elements and their states | | `unmountOnExit` | `boolean` | No | Whether to unmount on exit. | | `value` | `string[]` | No | The controlled value of the combobox's selected items | **Root Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | root | | `[data-invalid]` | Present when invalid | | `[data-readonly]` | Present when read-only | **ClearTrigger Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `Snippet<[PropsFn<'button'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `ref` | `Element` | No | | **ClearTrigger Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | clear-trigger | | `[data-invalid]` | Present when invalid | **Content Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `Snippet<[PropsFn<'div'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `ref` | `Element` | No | | **Content Data Attributes:** | Attribute | Value | |-----------|-------| | `[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-empty]` | Present when the content is empty | **Content CSS Variables:** | Variable | Description | |----------|-------------| | `--layer-index` | The index of the dismissable in the layer stack | | `--nested-layer-count` | The number of nested comboboxs | **Context Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `render` | `Snippet<[UseComboboxContext]>` | Yes | | **Control Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `Snippet<[PropsFn<'div'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `ref` | `Element` | No | | **Control Data Attributes:** | Attribute | Value | |-----------|-------| | `[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:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `Snippet<[PropsFn<'div'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `ref` | `Element` | No | | **Input Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `Snippet<[PropsFn<'input'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `ref` | `Element` | No | | **Input Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | input | | `[data-invalid]` | Present when invalid | | `[data-autofocus]` | | | `[data-state]` | "open" | "closed" | **ItemContext Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `render` | `Snippet<[UseComboboxItemContext]>` | Yes | | **ItemGroupLabel Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `Snippet<[PropsFn<'div'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `ref` | `Element` | No | | **ItemGroup Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `Snippet<[PropsFn<'div'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `id` | `string` | No | | | `ref` | `Element` | No | | **ItemGroup Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | item-group | | `[data-empty]` | Present when the content is empty | **ItemIndicator Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `Snippet<[PropsFn<'div'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `ref` | `Element` | No | | **ItemIndicator Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | item-indicator | | `[data-state]` | "checked" | "unchecked" | **Item Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `Snippet<[PropsFn<'div'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `item` | `{}` | No | | | `persistFocus` | `boolean` | No | | | `ref` | `Element` | No | | **Item Data Attributes:** | Attribute | Value | |-----------|-------| | `[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:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `Snippet<[PropsFn<'span'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `ref` | `Element` | No | | **ItemText Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | item-text | | `[data-state]` | "checked" | "unchecked" | | `[data-disabled]` | Present when disabled | | `[data-highlighted]` | Present when highlighted | **Label Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `Snippet<[PropsFn<'label'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `ref` | `Element` | No | | **Label Data Attributes:** | Attribute | Value | |-----------|-------| | `[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:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `Snippet<[PropsFn<'div'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `ref` | `Element` | No | | **List Data Attributes:** | Attribute | Value | |-----------|-------| | `[data-scope]` | combobox | | `[data-part]` | list | | `[data-empty]` | Present when the content is empty | **Positioner Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `Snippet<[PropsFn<'div'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `ref` | `Element` | No | | **Positioner CSS Variables:** | Variable | Description | |----------|-------------| | `--reference-width` | The width of the reference element | | `--reference-height` | The height of the root | | `--available-width` | The available width in viewport | | `--available-height` | The available height in viewport | | `--x` | The x position for transform | | `--y` | The y position for transform | | `--z-index` | The z-index value | | `--transform-origin` | The transform origin for animations | **RootProvider Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `value` | `UseComboboxReturn` | Yes | | | `asChild` | `Snippet<[PropsFn<'div'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `immediate` | `boolean` | No | Whether to synchronize the present change immediately or defer it to the next frame | | `lazyMount` | `boolean` | No | Whether to enable lazy mounting | | `onExitComplete` | `VoidFunction` | No | Function called when the animation ends in the closed state | | `present` | `boolean` | No | Whether the node is present (controlled by the user) | | `ref` | `Element` | No | | | `skipAnimationOnMount` | `boolean` | No | Whether to allow the initial presence animation. | | `unmountOnExit` | `boolean` | No | Whether to unmount on exit. | **Trigger Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `asChild` | `Snippet<[PropsFn<'button'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. | | `ref` | `Element` | No | | **Trigger Data Attributes:** | Attribute | Value | |-----------|-------| | `[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` | The value of the highlighted item | | `highlightedItem` | `V` | 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` | Function to toggle the combobox | | `reposition` | `(options?: Partial) => 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