Combobox
A single input field that combines the functionality of select and input.
'use client'
import {
Combobox,
Portal,
useFilter,
useListCollection,
} from '@fidely-ui/react'
export const ComboboxBasics = () => {
const { contains } = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems: frameworks,
filter: contains,
})
return (
<Combobox.Root
collection={collection}
onInputValueChange={(e) => filter(e.inputValue)}
>
<Combobox.Label>Select framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input placeholder="Type to search" />
<Combobox.IndicatorGroup>
<Combobox.ClearTrigger />
<Combobox.Trigger />
</Combobox.IndicatorGroup>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.Empty>Nothing found.</Combobox.Empty>
{collection.items.map((item) => (
<Combobox.Item item={item} key={item.value}>
{item.label}
<Combobox.ItemIndicator>β</Combobox.ItemIndicator>
</Combobox.Item>
))}
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
)
}
const frameworks = [
{ label: 'React', value: 'react' },
{ label: 'Next.js', value: 'nextjs' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Angular', value: 'angular' },
{ label: 'Svelte', value: 'svelte' },
{ label: 'Remix', value: 'remix' },
{ label: 'Laravel', value: 'laravel' },
]
Usage
import { Combobox } from '@fidely-ui/react'<Combobox.Root>
<Combobox.Label />
<Combobox.Control>
<Combobox.Input />
<Combobox.IndicatorGroup>
<Combobox.ClearTrigger />
<Combobox.Trigger />
</Combobox.IndicatorGroup>
</Combobox.Control>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.Empty />
<Combobox.Item />
<Combobox.ItemGroup>
<Combobox.ItemGroupLabel />
<Combobox.Item />
</Combobox.ItemGroup>
</Combobox.Content>
</Combobox.Positioner>
</Combobox.Root>Examples
Sizes
Use the size prop to adjust the size of the Select component.
'use client'
import {
Combobox,
Portal,
Stack,
useFilter,
useListCollection,
} from '@fidely-ui/react'
export const ComboboxSize = () => {
const { contains } = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems: frameworks,
filter: contains,
})
const sizes = ['xs', 'sm', 'md', 'lg'] as const
return (
<Stack gap={3}>
{sizes.map((size) => (
<Combobox.Root
collection={collection}
onInputValueChange={(e) => filter(e.inputValue)}
key={size}
size={size}
>
<Combobox.Label>Select framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input placeholder="Type to search" />
<Combobox.IndicatorGroup>
<Combobox.ClearTrigger />
<Combobox.Trigger />
</Combobox.IndicatorGroup>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.Empty>Nothing found.</Combobox.Empty>
{collection.items.map((item) => (
<Combobox.Item item={item} key={item.value}>
{item.label}
<Combobox.ItemIndicator>β</Combobox.ItemIndicator>
</Combobox.Item>
))}
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
))}
</Stack>
)
}
const frameworks = [
{ label: 'React', value: 'react' },
{ label: 'Next.js', value: 'nextjs' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Angular', value: 'angular' },
{ label: 'Svelte', value: 'svelte' },
{ label: 'Remix', value: 'remix' },
{ label: 'Laravel', value: 'laravel' },
]
Open on Click
Use the openOnClick prop to open the Combobox when the user clicks on the input.
'use client'
import {
Combobox,
Portal,
useFilter,
useListCollection,
} from '@fidely-ui/react'
export const ComboboxOpen = () => {
const { contains } = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems: frameworks,
filter: contains,
})
return (
<Combobox.Root
collection={collection}
onInputValueChange={(e) => filter(e.inputValue)}
openOnClick
>
<Combobox.Label>Select framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input placeholder="Type to search" />
<Combobox.IndicatorGroup>
<Combobox.ClearTrigger />
<Combobox.Trigger />
</Combobox.IndicatorGroup>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.Empty>Nothing found.</Combobox.Empty>
{collection.items.map((item) => (
<Combobox.Item item={item} key={item.value}>
{item.label}
<Combobox.ItemIndicator>β</Combobox.ItemIndicator>
</Combobox.Item>
))}
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
)
}
const frameworks = [
{ label: 'React', value: 'react' },
{ label: 'Next.js', value: 'nextjs' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Angular', value: 'angular' },
{ label: 'Svelte', value: 'svelte' },
{ label: 'Remix', value: 'remix' },
{ label: 'Laravel', value: 'laravel' },
]
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.
const items = [
{ name: 'JavaScript', id: 'js' },
{ name: 'TypeScript', id: 'ts' },
{ name: 'Python', id: 'py' },
{ name: 'Go', id: 'go' },
{ name: 'Rust', id: 'rs' },
]
const { contains } = useFilter({ sensitivity: 'base' })
const { collection } = useListCollection({
initialItems: items,
itemToString: (item) => item.name,
itemToValue: (item) => item.id,
filter: contains,
})With Persona
Use Combobox with Persona component

Sarah Thompson
UI/UX Designer

David Johnson
Frontend Engineer

Amelia Brown
Product Manager

James Carter
Backend Developer

Emily Wilson
Mobile Developer

Michael Anderson
DevOps Engineer

Sophia Martinez
QA Analyst

Liam Roberts
Full Stack Engineer

Olivia Parker
Data Scientist

Noah Davis
AI Researcher
'use client'
import {
Combobox,
Persona,
Portal,
useFilter,
useListCollection,
} from '@fidely-ui/react'
export const ComboboxWithPersona = () => {
const { contains } = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems: persons,
itemToString: (item) => item.name,
itemToValue: (item) => item.id,
filter: contains,
})
return (
<Combobox.Root
collection={collection}
onInputValueChange={(e) => filter(e.inputValue)}
openOnClick
>
<Combobox.Label>Select a person</Combobox.Label>
<Combobox.Control>
<Combobox.Input placeholder="Type a nameβ¦" />
<Combobox.IndicatorGroup>
<Combobox.ClearTrigger />
<Combobox.Trigger />
</Combobox.IndicatorGroup>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.Empty>No person found.</Combobox.Empty>
{collection.items.map((item) => (
<Combobox.Item key={item.id} item={item}>
<Persona
title={item.role}
name={item.name}
img={item.avatar}
imgSize="xs"
/>
<Combobox.ItemIndicator>β</Combobox.ItemIndicator>
</Combobox.Item>
))}
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
)
}
const persons = [
{
id: '1',
name: 'Sarah Thompson',
role: 'UI/UX Designer',
avatar: 'https://randomuser.me/api/portraits/women/44.jpg',
},
{
id: '2',
name: 'David Johnson',
role: 'Frontend Engineer',
avatar: 'https://randomuser.me/api/portraits/men/46.jpg',
},
{
id: '3',
name: 'Amelia Brown',
role: 'Product Manager',
avatar: 'https://randomuser.me/api/portraits/women/19.jpg',
},
{
id: '4',
name: 'James Carter',
role: 'Backend Developer',
avatar: 'https://randomuser.me/api/portraits/men/55.jpg',
},
{
id: '5',
name: 'Emily Wilson',
role: 'Mobile Developer',
avatar: 'https://randomuser.me/api/portraits/women/68.jpg',
},
{
id: '6',
name: 'Michael Anderson',
role: 'DevOps Engineer',
avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
},
{
id: '7',
name: 'Sophia Martinez',
role: 'QA Analyst',
avatar: 'https://randomuser.me/api/portraits/women/62.jpg',
},
{
id: '8',
name: 'Liam Roberts',
role: 'Full Stack Engineer',
avatar: 'https://randomuser.me/api/portraits/men/71.jpg',
},
{
id: '9',
name: 'Olivia Parker',
role: 'Data Scientist',
avatar: 'https://randomuser.me/api/portraits/women/53.jpg',
},
{
id: '10',
name: 'Noah Davis',
role: 'AI Researcher',
avatar: 'https://randomuser.me/api/portraits/men/29.jpg',
},
]
Minimum Characters
Use the openOnChange prop to set a minimum number of characters before filtering the list.
<Combobox.Root openOnChange={(e) => e.inputValue.length > 2} />Disabled
Pass the disabled prop to the Combobox.Root to disable the entire combobox.
'use client'
import {
Combobox,
Portal,
useFilter,
useListCollection,
} from '@fidely-ui/react'
export const ComboboxDisabled = () => {
const { contains } = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems: frameworks,
filter: contains,
})
return (
<Combobox.Root
collection={collection}
onInputValueChange={(e) => filter(e.inputValue)}
disabled
>
<Combobox.Label>Select framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input placeholder="Type to search" />
<Combobox.IndicatorGroup>
<Combobox.ClearTrigger />
<Combobox.Trigger />
</Combobox.IndicatorGroup>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.Empty>Nothing found.</Combobox.Empty>
{collection.items.map((item) => (
<Combobox.Item item={item} key={item.value}>
{item.label}
<Combobox.ItemIndicator>β</Combobox.ItemIndicator>
</Combobox.Item>
))}
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
)
}
const frameworks = [
{ label: 'React', value: 'react' },
{ label: 'Next.js', value: 'nextjs' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Angular', value: 'angular' },
{ label: 'Svelte', value: 'svelte' },
{ label: 'Remix', value: 'remix' },
{ label: 'Laravel', value: 'laravel' },
]
Disabled Item
Disable specific items in the dropdown, add the disabled prop to the collection item.
const items = [
{ label: 'Item 1', value: 'item-1', disabled: true },
{ label: 'Item 2', value: 'item-2' },
]
const { collection } = useListCollection({
initialItems: items,
})Store
An alternative way to control the Combobox component is to use the Combobox.RootProvider component and the useCombobox store hook.
import { Combobox, useCombobox } from '@fidely-ui/react'
function ComboboxDemo() {
const combobox = useCombobox()
return (
<Combobox.RootProvider value={combobox}>{/* ... */}</Combobox.RootProvider>
)
}This way you can access the combobox state and methods from outside the combobox.
'use client'
import {
Combobox,
Portal,
useCombobox,
useFilter,
useListCollection,
} from '@fidely-ui/react'
export const ComboboxStore = () => {
const { contains } = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems: frameworks,
filter: contains,
})
const combobox = useCombobox({
collection,
onInputValueChange(e) {
filter(e.inputValue)
},
})
return (
<Combobox.RootProvider value={combobox}>
<Combobox.Label>Select framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input placeholder="Type to search" />
<Combobox.IndicatorGroup>
<Combobox.ClearTrigger />
<Combobox.Trigger />
</Combobox.IndicatorGroup>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.Empty>Nothing found.</Combobox.Empty>
{collection.items.map((item) => (
<Combobox.Item item={item} key={item.value}>
{item.label}
<Combobox.ItemIndicator>β</Combobox.ItemIndicator>
</Combobox.Item>
))}
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.RootProvider>
)
}
const frameworks = [
{ label: 'React', value: 'react' },
{ label: 'Next.js', value: 'nextjs' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Angular', value: 'angular' },
{ label: 'Svelte', value: 'svelte' },
{ label: 'Remix', value: 'remix' },
{ label: 'Laravel', value: 'laravel' },
]
Limit 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.
'use client'
import { useRef } from 'react'
import {
Combobox,
Portal,
useFilter,
useListCollection,
} from '@fidely-ui/react'
export const ComboboxLimits = () => {
const contentRef = useRef<HTMLDivElement | null>(null)
const { startsWith } = useFilter({ sensitivity: 'base' })
const { collection, filter, reset } = useListCollection({
initialItems: frameworks,
filter: startsWith,
limit: 2,
})
return (
<Combobox.Root
collection={collection}
onInputValueChange={(e) => filter(e.inputValue)}
>
<Combobox.Label>Select framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input placeholder="Type to search" />
<Combobox.IndicatorGroup>
<Combobox.ClearTrigger />
<Combobox.Trigger onClick={reset} />
</Combobox.IndicatorGroup>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content ref={contentRef}>
<Combobox.Empty>Nothing found.</Combobox.Empty>
{collection.items.map((item) => (
<Combobox.Item item={item} key={item.value}>
{item.label}
<Combobox.ItemIndicator>β</Combobox.ItemIndicator>
</Combobox.Item>
))}
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
)
}
const frameworks = [
{ label: 'React', value: 'react' },
{ label: 'Next.js', value: 'nextjs' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Angular', value: 'angular' },
{ label: 'Svelte', value: 'svelte' },
{ label: 'Remix', value: 'remix' },
{ label: 'Laravel', value: 'laravel' },
]
Within Dialog
To use the Combobox within a dialog component, avoid wrapping the Combobox.Positioner within the Portal.
;-(
<Portal>
<Combobox.Positioner>
<Combobox.Content>{/* ... */}</Combobox.Content>
</Combobox.Positioner>
-
</Portal>
)If you use a Dialog and have set scrollBehavior="inside", you need to:
- Set the combobox positioning to fixed to avoid the combobox from being clipped by the dialog.
- Set hideWhenDetached to true to hide the combobox when the trigger is scrolled out of view.
<Combobox.Root positioning={{ strategy: 'fixed', hideWhenDetached: true }}>
{/* ... */}
</Combobox.Root>Props
Root
| Prop | Type | Default | Description |
|---|---|---|---|
allowCustomValue | boolean | β | Whether to allow typing custom values in the input. |
as | React.ElementType | β | The underlying element to render. |
asChild | boolean | β | Uses the provided child element as the rendered element, merging its props and behavior. |
autoFocus | boolean | β | Whether to autofocus the input on mount. |
closeOnSelect | boolean | β | Whether to close the combobox when an item is selected. |
collection | ListCollection<T> | β | The collection of items. |
composite | boolean | true | Whether the combobox is composed with other composite widgets like tabs. |
defaultHighlightedValue | string | β | 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 | β | 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 | β | 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[] | [] | 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 | β | Whether the combobox is disabled. |
disableLayer | boolean | β | Whether to disable registering this a dismissable layer. |
form | string | β | The associate form of the combobox. |
highlightedValue | string | β | The controlled highlighted value of the combobox. |
id | string | β | The unique identifier of the component. |
immediate | boolean | β | Whether to synchronize the present change immediately or defer it to the next frame. |
inputBehavior | 'none' | 'autohighlight' | 'autocomplete' | none | 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 updates the input. |
inputValue | string | β | The controlled value of the combobox's input. |
invalid | boolean | β | Whether the combobox is invalid. |
loopFocus | boolean | true | Whether to loop the keyboard navigation through the items. |
multiple | boolean | β | 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 | β | The name attribute of the combobox's input. Useful for form submission. |
Maps | (details: NavigateDetails) => void | β | Function to navigate to the selected item. |
onFocusOutside | (event: FocusOutsideEvent) => void | β | Function called when the focus is moved outside the component. |
onInputValueChange | (details: OpenChangeDetails) => void | β | Function called when the input value changes. |
onPointerDownOutside | (event: PointerDownOutsideEvent) => void | β | Function called when the pointer is pressed down outside the component. |
onSelect | (details: SelectionDetails) => void | β | Function called when an item is selected. |
onValueChange | (details: ValueChangeDetails<T>) => void | β | Function called when a new item is selected. |
open | boolean | β | The controlled open state of the combobox. |
openOnChange | boolean | ((details: InputValueChangeDetails) => boolean) | true | Whether to open the combobox when the input value changes. |
openOnClick | boolean | false | Whether to open the combobox popup on initial click on the input. |
openOnKeyPress | boolean | true | Whether to open the combobox on arrow key press. |
placeholder | string | β | The placeholder text of the combobox's input. |
positioning | PositioningOptions | { placement: "bottom-start" } | The positioning options to dynamically position the menu. |
required | boolean | β | Whether the combobox is required. |
selectionBehavior | 'replace' | 'clear' | 'preserve' | 'replace' | The behavior of the combobox input when an item is selected. |
size | 'xs' | 'sm' | 'md' | 'lg' | md | The size of the component. |
translations | IntlTranslations | β | Specifies the localized strings that identifies the accessibility elements and their states. |
unmountOnExit | boolean | false | Whether to unmount on exit. |
value | string[] | β | The controlled value of the combobox's selected items. |
Item
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | β | Uses the provided child element as the rendered element, merging behavior. |
item | any | β | The item to render. |
persistFocus | boolean | β | Whether hovering outside should clear the highlighted state. |
as | boolean | β | The underlying element to render.. |