ComboBox
Documentation for the ComboBox component.
ComboBox
Preview
The ComboBox component is built with React Aria and styled using StyleX.
Import
import { ComboBox } from '@moul-dev/ui';Usage
Here is a basic example of how to use the ComboBox component:
import { ComboBox } from '@moul-dev/ui';
export default function Example() {
return (
<ComboBox>
{/* Component content */}
</ComboBox>
);
}Multi-select with TagGroup
You can build a multi-select ComboBox by managing the selection state in a list/set, filtering out the selected items from the dropdown, and rendering them as a TagGroup below the ComboBox.
import { ComboBox, ComboBoxItem, TagGroup, Tag } from '@moul-dev/ui';
import { useState } from 'react';
export default function Example() {
const provinces = [
{ id: 'phnom-penh', name: 'Phnom Penh' },
{ id: 'siem-reap', name: 'Siem Reap' },
{ id: 'battambang', name: 'Battambang' },
{ id: 'sihanoukville', name: 'Sihanoukville' },
{ id: 'kampot', name: 'Kampot' },
{ id: 'kandal', name: 'Kandal' },
{ id: 'kampong-cham', name: 'Kampong Cham' },
{ id: 'koh-kong', name: 'Koh Kong' },
{ id: 'kep', name: 'Kep' },
];
const [selectedKeys, setSelectedKeys] = useState<Set<any>>(new Set());
const [inputValue, setInputValue] = useState('');
const availableProvinces = provinces.filter(
(p) => !selectedKeys.has(p.id)
);
const handleSelectionChange = (key: any) => {
if (key) {
setSelectedKeys((prev) => {
const next = new Set(prev);
next.add(key);
return next;
});
setInputValue('');
}
};
const handleRemove = (keys: Set<any>) => {
setSelectedKeys((prev) => {
const next = new Set(prev);
for (const k of keys) {
next.delete(k);
}
return next;
});
};
return (
<div className="w-full max-w-sm flex flex-col gap-4">
<ComboBox
label="Cambodian Provinces"
placeholder="Select a province"
inputValue={inputValue}
onInputChange={setInputValue}
onSelectionChange={handleSelectionChange}
selectedKey={null}
>
{availableProvinces.map((province) => (
<ComboBoxItem key={province.id} id={province.id}>
{province.name}
</ComboBoxItem>
))}
</ComboBox>
{selectedKeys.size > 0 && (
<TagGroup
label="Selected Provinces"
onRemove={handleRemove}
variant="primary"
>
{[...selectedKeys].map((key) => {
const province = provinces.find((p) => p.id === key);
return (
<Tag key={key} id={key}>
{province?.name || key}
</Tag>
);
})}
</TagGroup>
)}
</div>
);
}Item Actions
Use the onAction prop on <ComboBoxItem> to perform custom actions when an item is pressed (such as dynamic "Create" actions). Custom action items bypass selection and will not populate the input.
Pair this with allowsEmptyCollection so the menu stays open when typing a query with no existing matches.
You can also pass onAction to <ComboBox> to be notified when any option is activated while preserving normal value selection.
import { ComboBox, ComboBoxItem } from '@moul-dev/ui';
import { useState } from 'react';
export default function Example() {
const [items, setItems] = useState([
{ id: 'react', name: 'React' },
{ id: 'vue', name: 'Vue' },
{ id: 'svelte', name: 'Svelte' },
]);
const [inputValue, setInputValue] = useState('');
const isExisting = items.some(
(item) => item.name.toLowerCase() === inputValue.trim().toLowerCase()
);
const handleCreate = (name: string) => {
const trimmed = name.trim();
if (!trimmed) return;
const id = trimmed.toLowerCase().replace(/\s+/g, '-');
setItems((prev) => [...prev, { id, name: trimmed }]);
setInputValue('');
};
return (
<ComboBox
label="Frontend Framework"
placeholder="Type or select..."
allowsEmptyCollection
inputValue={inputValue}
onInputChange={setInputValue}
onAction={(key) => console.log('Activated item key:', key)}
>
{inputValue.trim().length > 0 && !isExisting && (
<ComboBoxItem
id="create-new"
onAction={() => handleCreate(inputValue)}
>
{`Create "${inputValue.trim()}"`}
</ComboBoxItem>
)}
{items.map((item) => (
<ComboBoxItem key={item.id} id={item.id}>
{item.name}
</ComboBoxItem>
))}
</ComboBox>
);
}Props
ComboBox Props
Prop
Type
ComboBoxItem Props
Prop
Type
Default className: react-aria-ComboBox
| Render Prop | CSS Selector |
|---|---|
isOpen Whether the combobox list popover is open. | [data-open] |
isFocused Whether the combobox is focused. | [data-focused] |
isFocusVisible Whether the combobox is keyboard focused. | [data-focus-visible] |
isDisabled Whether the combobox is disabled. | [data-disabled] |