# 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** ```ripple import { Combobox, useListCollection } from 'ark-ripple/combobox'; import { useFilter } from 'ark-ripple/locale'; import { Portal } from 'ark-ripple/portal'; import { Check, ChevronsUpDown, X } from 'lucide-ripple'; import styles from 'styles/combobox.module.css'; export component Basic() { const filterApi = useFilter({ sensitivity: 'base' }); let { 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: @filterApi.contains, }, ); { filter(details.inputValue); }} > {'Favorite Fruit'}
for (const item of @collection.items; key item.value) { {item.label} }
} ``` ### Auto Highlight Automatically highlight the first matching item as the user types by setting `inputBehavior="autohighlight"`. **Example: auto-highlight** ```ripple import { Combobox, useListCollection } from 'ark-ripple/combobox'; import { useFilter } from 'ark-ripple/locale'; import { Portal } from 'ark-ripple/portal'; import { Check, ChevronsUpDown, X } from 'lucide-ripple'; import styles from 'styles/combobox.module.css'; export component AutoHighlight() { const filterApi = useFilter({ sensitivity: 'base' }); let { 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: @filterApi.contains, }, ); const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue); }; {'Department'}
{'No results found'} for (const item of @collection.items; key item.value) { {item.label} }
} ``` ### 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** ```ripple import { Combobox, useListCollection } from 'ark-ripple/combobox'; import { useFilter } from 'ark-ripple/locale'; import { Portal } from 'ark-ripple/portal'; import { Check, ChevronsUpDown, X } from 'lucide-ripple'; import styles from 'styles/combobox.module.css'; export component InlineAutocomplete() { const filterApi = useFilter({ sensitivity: 'base' }); let { 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: @filterApi.startsWith, }, ); const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue); }; {'Sea Creature'}
{'No results found'} for (const item of @collection.items; key item.value) { {item.label} }
} ``` ### Grouping To group related combobox items, use the `groupBy` prop on the collection and `collection.group()` to iterate the groups. **Example: grouping** ```ripple import { Combobox, useListCollection } from 'ark-ripple/combobox'; import { useFilter } from 'ark-ripple/locale'; import { Portal } from 'ark-ripple/portal'; import { Check, ChevronsUpDown, X } from 'lucide-ripple'; import styles from 'styles/combobox.module.css'; export component Grouping() { const filterApi = useFilter({ sensitivity: 'base' }); let { collection, filter } = useListCollection( { initialItems, filter: @filterApi.contains, groupBy: (item) => item.continent, }, ); const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue); }; {'Country'}
for (const [continent, group] of @collection.group(); key continent) { {continent} for (const item of group; key item.value) { {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' }, ]; ``` ### 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** ```ripple import { Combobox, useListCollection } from 'ark-ripple/combobox'; import { Field } from 'ark-ripple/field'; import { useFilter } from 'ark-ripple/locale'; import { Check, ChevronsUpDown, X } from 'lucide-ripple'; import field from 'styles/field.module.css'; import styles from 'styles/combobox.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 component WithField() { const filterApi = useFilter({ sensitivity: 'base' }); let { collection, filter } = useListCollection( { initialItems, filter: @filterApi.contains, }, ); const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue); }; {'Department'}
for (const item of @collection.items; key item.value) { {item.label} }
{'Select your primary department'} {'Department is required'}
} ``` ### Context Access the combobox's state with `Combobox.Context` or the `useComboboxContext` hookโ€”useful for displaying the selected value or building custom UI. **Example: context** ```ripple import { Combobox, useListCollection } from 'ark-ripple/combobox'; import { useFilter } from 'ark-ripple/locale'; import { Portal } from 'ark-ripple/portal'; import { Check, ChevronsUpDown, X } from 'lucide-ripple'; import styles from 'styles/combobox.module.css'; export component Context() { const filterApi = useFilter({ sensitivity: 'base' }); let { collection, filter } = useListCollection( { initialItems: [ { label: 'Small', value: 'sm' }, { label: 'Medium', value: 'md' }, { label: 'Large', value: 'lg' }, { label: 'Extra Large', value: 'xl' }, ], filter: @filterApi.contains, }, ); const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue); }; component children({ context }) {

{'Selected: '} {@context.valueAsString || 'None'}

}
{'Size'}
for (const item of @collection.items; key item.value) { {item.label} }
} ``` ### 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** ```ripple import { Combobox, useCombobox, useListCollection } from 'ark-ripple/combobox'; import { useFilter } from 'ark-ripple/locale'; import { Portal } from 'ark-ripple/portal'; import { Check, ChevronsUpDown, X } from 'lucide-ripple'; 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 component RootProvider() { const filterApi = useFilter({ sensitivity: 'base' }); let { collection, filter } = useListCollection( { initialItems, filter: @filterApi.contains, }, ); const combobox = useCombobox( { collection, onInputValueChange: (details) => { filter(details.inputValue); }, }, );
{'Job Title'}
for (const item of @collection.items; key item.value) { {item.label} }
} ``` ### Links Use the `asChild` prop to render the combobox items as links. **Example: links** ```ripple import { Combobox, useListCollection } from 'ark-ripple/combobox'; import { useFilter } from 'ark-ripple/locale'; import { Portal } from 'ark-ripple/portal'; import { Check, ChevronsUpDown } from 'lucide-ripple'; import styles from 'styles/combobox.module.css'; export component Links() { const filterApi = useFilter({ sensitivity: 'base' }); let { collection, filter } = useListCollection( { initialItems, filter: @filterApi.contains, }, ); const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue); }; {'Developer Resources'}
for (const item of @collection.items; key item.value) { component asChild({ propsFn }) { {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' }, ]; ``` ### 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** ```ripple import { Combobox, useCombobox, useComboboxContext, useListCollection } from 'ark-ripple/combobox'; import { Portal } from 'ark-ripple/portal'; import { Check } from 'lucide-ripple'; import { track, effect } from 'ripple'; import styles from 'styles/combobox.module.css'; import { useAsync } from './use-async'; export component RehydrateValue() { let inputValue = track(''); let { 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) => { @inputValue = e.inputValue; }, }, ); let 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); }); effect(() => { void @inputValue; void state.load(); }); let hydrated = false; effect(() => { if (@combobox.value.length && @combobox.collection.size && !hydrated) { @combobox.syncSelectedItems(); hydrated = true; } }); {'Search Star Wars Characters'} if (state.@loading) { {'Loading...'} } else if (state.@error) { {state.@error?.message} } else { for (const item of @collection.items; key item.name) { {item.name} {' - '} {item.height} {'cm / '} {item.mass} {'kg'} } } } interface Character { name: string; height: string; mass: string; created: string; edited: string; url: string; } ``` ### Highlight Text Highlight the matching search text in combobox items based on the user's input. **Example: highlight-matching-text** ```ripple import { Combobox, useListCollection } from 'ark-ripple/combobox'; import { Highlight } from 'ark-ripple/highlight'; import { useFilter } from 'ark-ripple/locale'; import { Portal } from 'ark-ripple/portal'; import { ChevronsUpDown, X } from 'lucide-ripple'; import styles from 'styles/combobox.module.css'; export component HighlightMatchingText() { const filterApi = useFilter({ sensitivity: 'base' }); let { 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: @filterApi.contains, }, ); const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue); }; {'Assignee'}
for (const item of @collection.items; key item.value) { component children({ context }) { } }
} ``` ### Dynamic Generate combobox items dynamically based on user input. This is useful for creating suggestions or autocomplete functionality. **Example: dynamic** ```ripple import { Combobox, useListCollection } from 'ark-ripple/combobox'; import { Portal } from 'ark-ripple/portal'; import { Check, ChevronsUpDown } from 'lucide-ripple'; import styles from 'styles/combobox.module.css'; const suggestList = ['gmail.com', 'yahoo.com', 'ark-ui.rip']; export component Dynamic() { let { collection, set } = useListCollection( { initialItems: [], }, ); const handleInputChange = (details: Combobox.InputValueChangeDetails) => { if (details.reason === 'input-change') { const items = suggestList.map((item) => `${details.inputValue}@${item}`); set(items); } }; {'Email'}
for (const item of @collection.items; key item) { {item} }
} ``` ### 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** ```ripple import { Combobox, useListCollection } from 'ark-ripple/combobox'; import { useFilter } from 'ark-ripple/locale'; import { Portal } from 'ark-ripple/portal'; import { Check, ChevronsUpDown, X } from 'lucide-ripple'; import styles from 'styles/combobox.module.css'; interface Item { label: string; value: string; __new__?: boolean | undefined; } 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 component Creatable() { const filterApi = useFilter({ sensitivity: 'base' }); let { collection, filter, upsert, remove, update } = useListCollection( { initialItems: [ { label: 'Bug', value: 'bug' }, { label: 'Feature', value: 'feature' }, { label: 'Enhancement', value: 'enhancement' }, { label: 'Documentation', value: 'docs' }, ], filter: @filterApi.contains, }, ); const isValidNewOption = (inputValue: string) => { const exactOptionMatch = @collection.filter((item) => item.toLowerCase() === inputValue.toLowerCase()).size > 0; return !exactOptionMatch && inputValue.trim().length > 0; }; let selectedValue = track([]); let inputValue = track(''); { if (details.reason === 'input-change' || details.reason === 'item-select') { if (isValidNewOption(details.inputValue)) { upsert(NEW_OPTION_VALUE, createNewOption(details.inputValue)); } else if (details.inputValue.trim().length === 0) { remove(NEW_OPTION_VALUE); } filter(details.inputValue); } @inputValue = details.inputValue; }} onOpenChange={(details) => { if (details.reason === 'trigger-click') { filter(''); } }} onValueChange={(details) => { @selectedValue = replaceNewOptionValue(details.value, @inputValue); if (details.value.includes(NEW_OPTION_VALUE)) { update(NEW_OPTION_VALUE, getNewOptionData(@inputValue)); } }} > {'Label'}
for (const item of @collection.items; key item.value) { if (isNewOptionValue(item.value)) { {'+ Create "'} {item.label} {'"'} } else { {item.label} {item.__new__ ? ' (new)' : ''} } }
} ``` ### Multiple Selection Enable multiple selection by setting the `multiple` prop. Selected items can be displayed as tags above the input. **Example: multiple** ```ripple import { Combobox, useListCollection } from 'ark-ripple/combobox'; import { useFilter } from 'ark-ripple/locale'; import { Portal } from 'ark-ripple/portal'; import { Check, ChevronsUpDown } from 'lucide-ripple'; import styles from 'styles/combobox.module.css'; export component Multiple() { const filterApi = useFilter({ sensitivity: 'base' }); let { 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: @filterApi.contains, }, ); { filter(details.inputValue); }} onValueChange={(details) => { remove(...details.value); }} multiple > {'Skills'} component children({ context }) {
if (@context.selectedItems.length === 0) { {'None selected'} } for (const item of @context.selectedItems; key item.value) { {item.label} }
}
{'No skills found'} for (const item of @collection.items; key item.value) { {item.label} }
} ``` ### 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** ```ripple import { useAsyncList } from 'ark-ripple/collection'; import { Combobox, createListCollection } from 'ark-ripple/combobox'; import { Portal } from 'ark-ripple/portal'; import { Check, ChevronsUpDown, Loader, X } from 'lucide-ripple'; import { track } from 'ripple'; import styles from 'styles/combobox.module.css'; interface Movie { id: string; title: string; year: number; director: string; genre: string; } export component AsyncSearch() { const list = useAsyncList( { async load({ filterText, signal }: { filterText?: string; signal?: AbortSignal }) { 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 = track( () => createListCollection( { items: @list.items, itemToString: (item) => item.title, itemToValue: (item) => item.id, }, ), ); { if (details.reason === 'input-change') { @list.setFilterText(details.inputValue); } }} > {'Movie'}
if (@list.loading) {
{'Searching...'}
} else if (@list.error) {
{@list.error.message}
} else if (@list.items.length === 0) {
{@list.filterText ? 'No results found' : 'Start typing to search movies...'}
} else { for (const movie of @list.items; key movie.id) { {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** ```ripple import { Combobox, useListCollection } from 'ark-ripple/combobox'; import { useFilter } from 'ark-ripple/locale'; import { Portal } from 'ark-ripple/portal'; import { Check, ChevronsUpDown } from 'lucide-ripple'; import styles from 'styles/combobox.module.css'; import { createVirtualizer } from '../../../utils/use-virtualizer.ripple'; import { flushSync, track } from 'ripple'; export component Virtualized() { let contentRef = track(null); const filterApi = useFilter({ sensitivity: 'base' }); let { collection, filter, reset } = useListCollection( { initialItems: countries, filter: @filterApi.startsWith, }, ); const virtualizer = createVirtualizer( { get count() { return @collection.size; }, getScrollElement: () => @contentRef, 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); }; {'Country'}
{ @contentRef = el; return () => { @contentRef = null; }; }} class={styles.Scroller} >
for (const virtualItem of virtualizer.getVirtualItems(); key ((@collection.items[virtualItem.index])).value) { let item = @collection.items[virtualItem.index]; {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: '๐Ÿ‡ฟ๐Ÿ‡ผ' }, ]; ``` ### Custom Object Use the `itemToString` and `itemToValue` props to map custom objects to the required interface. **Example: custom-object** ```ripple import { Combobox, useListCollection } from 'ark-ripple/combobox'; import { useFilter } from 'ark-ripple/locale'; import { Portal } from 'ark-ripple/portal'; import { Check, ChevronsUpDown, X } from 'lucide-ripple'; import styles from 'styles/combobox.module.css'; export component CustomObject() { const filterApi = useFilter({ sensitivity: 'base' }); let { 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: @filterApi.contains, }, ); const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue); }; {'Country'}
for (const item of @collection.items; key item.code) { {item.flag} {' '} {item.country} }
} ``` ### Limit Results Use the `limit` property on `useListCollection` to limit the number of rendered items in the DOM. **Example: limit-results** ```ripple import { Combobox, useListCollection } from 'ark-ripple/combobox'; import { useFilter } from 'ark-ripple/locale'; import { Portal } from 'ark-ripple/portal'; import { Check, ChevronsUpDown } from 'lucide-ripple'; 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 component LimitResults() { const filterApi = useFilter({ sensitivity: 'base' }); let { collection, filter } = useListCollection( { initialItems: cities, limit: 5, filter: @filterApi.contains, }, ); const handleInputChange = (details: Combobox.InputValueChangeDetails) => { filter(details.inputValue); }; {'City'}
for (const item of @collection.items; key item.value) { {item.label} }
} ``` ## Guides ### Router Links Customize the `navigate` prop on `Combobox.Root` to integrate with your router.: ```tsx import { Combobox } from 'ark-ripple/combobox' import { useNavigate } from '' 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, }) ``` ### 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** **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 | ### 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