# Combobox A combobox is an input widget with an associated popup that enables users to select a value from a collection of possible values. ```tsx import { Combobox, Portal, type ComboboxRootProps, useListCollection } from '@skeletonlabs/skeleton-react'; import { useState } from 'react'; const data = [ { label: 'Apple', value: 'apple' }, { label: 'Banana', value: 'banana' }, { label: 'Orange', value: 'orange' }, { label: 'Carrot', value: 'carrot' }, { label: 'Broccoli', value: 'broccoli' }, { label: 'Spinach', value: 'spinach' }, ]; export default function Default() { const [items, setItems] = useState(data); const collection = useListCollection({ items: items, itemToString: (item) => item.label, itemToValue: (item) => item.value, }); const onOpenChange = () => { setItems(data); }; const onInputValueChange: ComboboxRootProps['onInputValueChange'] = (event) => { const filtered = data.filter((item) => item.value.toLowerCase().includes(event.inputValue.toLowerCase())); if (filtered.length > 0) { setItems(filtered); } else { setItems(data); } }; return ( Label {items.map((item) => ( {item.label} ))} ); } ``` ## Groups Create labelled groups for your items. ```tsx 'use client'; import { Combobox, Portal, type ComboboxRootProps, useListCollection } from '@skeletonlabs/skeleton-react'; import { useState } from 'react'; const data = [ { label: 'Apple', value: 'apple', type: 'Fruits' }, { label: 'Banana', value: 'banana', type: 'Fruits' }, { label: 'Orange', value: 'orange', type: 'Fruits' }, { label: 'Carrot', value: 'carrot', type: 'Vegetables' }, { label: 'Broccoli', value: 'broccoli', type: 'Vegetables' }, { label: 'Spinach', value: 'spinach', type: 'Vegetables' }, ]; export default function Group() { const [items, setItems] = useState(data); const collection = useListCollection({ items: items, itemToString: (item) => item.label, itemToValue: (item) => item.value, groupBy: (item) => item.type, }); const onOpenChange = () => { setItems(data); }; const onInputValueChange: ComboboxRootProps['onInputValueChange'] = (event) => { const filtered = data.filter((item) => item.value.toLowerCase().includes(event.inputValue.toLowerCase())); if (filtered.length > 0) { setItems(filtered); } else { setItems(data); } }; return ( {collection.group().map(([type, items]) => ( {type} {items.map((item) => ( {item.label} ))} ))} ); } ``` ## Auto Highlight Search for any option, then tap Enter on your keyboard to automatically select it. ```tsx import { Combobox, Portal, type ComboboxRootProps, useListCollection } from '@skeletonlabs/skeleton-react'; import { useState } from 'react'; const data = [ { label: 'Apple', value: 'apple' }, { label: 'Banana', value: 'banana' }, { label: 'Orange', value: 'orange' }, { label: 'Carrot', value: 'carrot' }, { label: 'Broccoli', value: 'broccoli' }, { label: 'Spinach', value: 'spinach' }, ]; export default function AutoHighlight() { const [items, setItems] = useState(data); const collection = useListCollection({ items: items, itemToString: (item) => item.label, itemToValue: (item) => item.value, }); const onOpenChange = () => { setItems(data); }; const onInputValueChange: ComboboxRootProps['onInputValueChange'] = (event) => { const filtered = data.filter((item) => item.value.toLowerCase().includes(event.inputValue.toLowerCase())); if (filtered.length > 0) { setItems(filtered); } else { setItems(data); } }; return ( {items.map((item) => ( {item.label} ))} ); } ``` ## Multiple To maintain filtering functionality and improve clarity for users, we recommend displaying each selected value outside the perimeter of the Combobox component. ```tsx import { Combobox, Portal, type ComboboxRootProps, useListCollection } from '@skeletonlabs/skeleton-react'; import { useState } from 'react'; const data = [ { label: 'Apple', value: 'apple' }, { label: 'Banana', value: 'banana' }, { label: 'Orange', value: 'orange' }, { label: 'Carrot', value: 'carrot' }, { label: 'Broccoli', value: 'broccoli' }, { label: 'Spinach', value: 'spinach' }, ]; export default function Default() { const [value, setValue] = useState([]); const [items, setItems] = useState(data); const collection = useListCollection({ items: items, itemToString: (item) => item.label, itemToValue: (item) => item.value, }); const onOpenChange = () => { setItems(data); }; const onInputValueChange: ComboboxRootProps['onInputValueChange'] = (event) => { const filtered = data.filter((item) => item.value.toLowerCase().includes(event.inputValue.toLowerCase())); if (filtered.length > 0) { setItems(filtered); } else { setItems(data); } }; const onValueChange: ComboboxRootProps['onValueChange'] = (event) => { setValue(event.value); }; return (
{items.map((item) => ( {item.label} ))}
{value.map((item) => ( {item} ))}
); } ``` ## Disabled Item ```tsx import { Combobox, Portal, type ComboboxRootProps, useListCollection } from '@skeletonlabs/skeleton-react'; import { useState } from 'react'; const data = [ { label: 'Apple', value: 'apple' }, { label: 'Banana', value: 'banana' }, { label: 'Orange', value: 'orange' }, { label: 'Carrot', value: 'carrot' }, { label: 'Broccoli', value: 'broccoli' }, { label: 'Spinach', value: 'spinach' }, ]; export default function Default() { const [items, setItems] = useState(data); const collection = useListCollection({ items: items, itemToString: (item) => item.label, itemToValue: (item) => item.value, isItemDisabled: (item) => item.value === 'banana', }); const onOpenChange = () => { setItems(data); }; const onInputValueChange: ComboboxRootProps['onInputValueChange'] = (event) => { const filtered = data.filter((item) => item.value.toLowerCase().includes(event.inputValue.toLowerCase())); if (filtered.length > 0) { setItems(filtered); } else { setItems(data); } }; return ( {items.map((item) => ( {item.label} ))} ); } ``` ## Custom Filter Try mistyping `apple` or `banana` to see the custom filter using the fuzzy search from [Fuse.js](https://fusejs.io/) in action. ```tsx import { Combobox, Portal, type ComboboxRootProps, useListCollection } from '@skeletonlabs/skeleton-react'; import Fuse from 'fuse.js'; import { useState } from 'react'; const data = [ { label: 'Apple', value: 'apple' }, { label: 'Banana', value: 'banana' }, { label: 'Orange', value: 'orange' }, { label: 'Carrot', value: 'carrot' }, { label: 'Broccoli', value: 'broccoli' }, { label: 'Spinach', value: 'spinach' }, ]; const fuse = new Fuse(data, { keys: ['label', 'value'], threshold: 0.3, }); export default function Default() { const [items, setItems] = useState(data); const collection = useListCollection({ items: items, itemToString: (item) => item.label, itemToValue: (item) => item.value, }); const onOpenChange = () => { setItems(data); }; const onInputValueChange: ComboboxRootProps['onInputValueChange'] = (event) => { const results = fuse.search(event.inputValue); if (results.length > 0) { setItems(results.map((result) => result.item)); } else { setItems(data); } }; return ( {items.map((item) => ( {item.label} ))} ); } ``` ## Direction ```tsx import { Combobox, Portal, type ComboboxRootProps, useListCollection } from '@skeletonlabs/skeleton-react'; import { useState } from 'react'; const data = [ { label: 'Apple', value: 'apple' }, { label: 'Banana', value: 'banana' }, { label: 'Orange', value: 'orange' }, { label: 'Carrot', value: 'carrot' }, { label: 'Broccoli', value: 'broccoli' }, { label: 'Spinach', value: 'spinach' }, ]; export default function Dir() { const [items, setItems] = useState(data); const collection = useListCollection({ items: items, itemToString: (item) => item.label, itemToValue: (item) => item.value, }); const onOpenChange = () => { setItems(data); }; const onInputValueChange: ComboboxRootProps['onInputValueChange'] = (event) => { const filtered = data.filter((item) => item.value.toLowerCase().includes(event.inputValue.toLowerCase())); if (filtered.length > 0) { setItems(filtered); } else { setItems(data); } }; return ( Label {items.map((item) => ( {item.label} ))} ); } ``` ## Guidelines ### Z-Index By default we do not take an opinionated stance regarding z-index stacking. The result is the component can sometimes be occluded beneath other elements with a higher index. The Z-Index can controlled by applying a utility class to the Positioner component part. ```tsx import { Combobox, Portal, type ComboboxRootProps, useListCollection } from '@skeletonlabs/skeleton-react'; import { useState } from 'react'; const data = [ { label: 'Apple', value: 'apple' }, { label: 'Banana', value: 'banana' }, { label: 'Orange', value: 'orange' }, { label: 'Carrot', value: 'carrot' }, { label: 'Broccoli', value: 'broccoli' }, { label: 'Spinach', value: 'spinach' }, ]; export default function ZIndex() { const [items, setItems] = useState(data); const collection = useListCollection({ items: items, itemToString: (item) => item.label, itemToValue: (item) => item.value, }); const onOpenChange = () => { setItems(data); }; const onInputValueChange: ComboboxRootProps['onInputValueChange'] = (event) => { const filtered = data.filter((item) => item.value.toLowerCase().includes(event.inputValue.toLowerCase())); if (filtered.length > 0) { setItems(filtered); } else { setItems(data); } }; return ( {items.map((item) => ( {item.label} ))} ); } ``` ### Max Items We recommend no more than 500 items max. For normal usage, a few dozen will provide the best performance. ## API Reference ### ComboboxRootProps | Property | Default | Type | Description | | ------------------------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | open? | - | boolean \| undefined | The controlled open state of the combobox | | defaultOpen? | - | boolean \| undefined | The initial open state of the combobox when rendered. Use when you don't need to control the open state 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; }> \| undefined | The ids of the elements in the combobox. Useful for composition. | | inputValue? | - | string \| undefined | The controlled value of the combobox's input | | defaultInputValue? | "" | string \| undefined | 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. | | name? | - | string \| undefined | The \`name\` attribute of the combobox's input. Useful for form submission | | form? | - | string \| undefined | The associate form of the combobox. | | disabled? | - | boolean \| undefined | Whether the combobox is disabled | | readOnly? | - | boolean \| undefined | Whether the combobox is readonly. This puts the combobox in a "non-editable" mode but the user can still interact with it | | invalid? | - | boolean \| undefined | Whether the combobox is invalid | | required? | - | boolean \| undefined | Whether the combobox is required | | placeholder? | - | string \| undefined | The placeholder text of the combobox's input | | defaultHighlightedValue? | - | string \| null \| undefined | The initial highlighted value of the combobox when rendered. Use when you don't need to control the highlighted value of the combobox. | | highlightedValue? | - | string \| null \| undefined | The controlled highlighted value of the combobox | | value? | - | string\[] \| undefined | The controlled value of the combobox's selected items | | defaultValue? | \[] | string\[] \| undefined | 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. | | inputBehavior? | "none" | "autohighlight" \| "autocomplete" \| "none" \| undefined | 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 | | selectionBehavior? | "replace" | "clear" \| "replace" \| "preserve" \| undefined | 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 | | autoFocus? | - | boolean \| undefined | Whether to autofocus the input on mount | | openOnClick? | false | boolean \| undefined | Whether to open the combobox popup on initial click on the input | | openOnChange? | true | boolean \| ((details: InputValueChangeDetails) => boolean) \| undefined | Whether to show the combobox when the input value changes | | allowCustomValue? | - | boolean \| undefined | Whether to allow typing custom values in the input | | alwaysSubmitOnEnter? | false | boolean \| undefined | 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. | | loopFocus? | true | boolean \| undefined | Whether to loop the keyboard navigation through the items | | positioning? | \{ placement: "bottom-start" } | PositioningOptions \| undefined | The positioning options to dynamically position the menu | | onInputValueChange? | - | ((details: InputValueChangeDetails) => void) \| undefined | Function called when the input's value changes | | onValueChange? | - | ((details: ValueChangeDetails\) => void) \| undefined | Function called when a new item is selected | | onHighlightChange? | - | ((details: HighlightChangeDetails\) => void) \| undefined | Function called when an item is highlighted using the pointer or keyboard navigation. | | onSelect? | - | ((details: SelectionDetails) => void) \| undefined | Function called when an item is selected | | onOpenChange? | - | ((details: OpenChangeDetails) => void) \| undefined | Function called when the popup is opened | | translations? | - | IntlTranslations \| undefined | Specifies the localized strings that identifies the accessibility elements and their states | | collection? | - | ListCollection\ \| undefined | The collection of items | | multiple? | - | boolean \| undefined | 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. | | closeOnSelect? | - | boolean \| undefined | Whether to close the combobox when an item is selected. | | openOnKeyPress? | true | boolean \| undefined | Whether to open the combobox on arrow key press | | scrollToIndexFn? | - | ((details: ScrollToIndexDetails) => void) \| undefined | Function to scroll to a specific index | | composite? | true | boolean \| undefined | Whether the combobox is a composed with other composite widgets like tabs | | disableLayer? | - | boolean \| undefined | Whether to disable registering this a dismissable layer | | navigate? | - | ((details: NavigateDetails) => void) \| null \| undefined | Function to navigate to the selected item | | dir? | "ltr" | "ltr" \| "rtl" \| undefined | The document's text/writing direction. | | getRootNode? | - | (() => ShadowRoot \| Node \| Document) \| undefined | A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron. | | onPointerDownOutside? | - | ((event: PointerDownOutsideEvent) => void) \| undefined | Function called when the pointer is pressed down outside the component | | onFocusOutside? | - | ((event: FocusOutsideEvent) => void) \| undefined | Function called when the focus is moved outside the component | | onInteractOutside? | - | ((event: InteractOutsideEvent) => void) \| undefined | Function called when an interaction happens outside the component | | element? | - | ((attributes: HTMLAttributes\<"div">) => Element) \| undefined | Render the element yourself | ### ComboboxRootProviderProps | Property | Default | Type | Description | | -------- | ------- | -------------------------------------------------------------- | --------------------------- | | value | - | ComboboxApi\ | - | | element? | - | ((attributes: HTMLAttributes\<"div">) => Element) \| undefined | Render the element yourself | ### ComboboxRootContextProps | Property | Default | Type | Description | | -------- | ------- | ----------------------------------------------------- | ----------- | | children | - | (combobox: ComboboxApi\) => ReactNode | - | ### ComboboxLabelProps | Property | Default | Type | Description | | -------- | ------- | ---------------------------------------------------------------- | --------------------------- | | element? | - | ((attributes: HTMLAttributes\<"label">) => Element) \| undefined | Render the element yourself | ### ComboboxControlProps | Property | Default | Type | Description | | -------- | ------- | -------------------------------------------------------------- | --------------------------- | | element? | - | ((attributes: HTMLAttributes\<"div">) => Element) \| undefined | Render the element yourself | ### ComboboxInputProps | Property | Default | Type | Description | | -------- | ------- | ---------------------------------------------------------------- | --------------------------- | | element? | - | ((attributes: HTMLAttributes\<"input">) => Element) \| undefined | Render the element yourself | ### ComboboxTriggerProps | Property | Default | Type | Description | | -------- | ------- | ----------------------------------------------------------------- | --------------------------- | | element? | - | ((attributes: HTMLAttributes\<"button">) => Element) \| undefined | Render the element yourself | ### ComboboxPositionerProps | Property | Default | Type | Description | | -------- | ------- | -------------------------------------------------------------- | --------------------------- | | element? | - | ((attributes: HTMLAttributes\<"div">) => Element) \| undefined | Render the element yourself | ### ComboboxContentProps | Property | Default | Type | Description | | -------- | ------- | ------------------------------------------------------------- | --------------------------- | | element? | - | ((attributes: HTMLAttributes\<"ul">) => Element) \| undefined | Render the element yourself | ### ComboboxItemGroupProps | Property | Default | Type | Description | | -------- | ------- | -------------------------------------------------------------- | --------------------------- | | element? | - | ((attributes: HTMLAttributes\<"div">) => Element) \| undefined | Render the element yourself | ### ComboboxItemGroupLabelProps | Property | Default | Type | Description | | -------- | ------- | -------------------------------------------------------------- | --------------------------- | | element? | - | ((attributes: HTMLAttributes\<"div">) => Element) \| undefined | Render the element yourself | ### ComboboxItemProps | Property | Default | Type | Description | | ------------- | ------- | ------------------------------------------------------------- | ----------------------------------------------------------- | | persistFocus? | - | boolean \| undefined | Whether hovering outside should clear the highlighted state | | item | - | any | The item to render | | element? | - | ((attributes: HTMLAttributes\<"li">) => Element) \| undefined | Render the element yourself | ### ComboboxItemTextProps | Property | Default | Type | Description | | -------- | ------- | --------------------------------------------------------------- | --------------------------- | | element? | - | ((attributes: HTMLAttributes\<"span">) => Element) \| undefined | Render the element yourself | ### ComboboxItemIndicatorProps | Property | Default | Type | Description | | -------- | ------- | -------------------------------------------------------------- | --------------------------- | | element? | - | ((attributes: HTMLAttributes\<"div">) => Element) \| undefined | Render the element yourself |