Single-value picker with a searchable text input, built on Base UI Combobox. As the user types, the option list is filtered (accent-insensitive); options can also be loaded asynchronously.
Reach for Combobox when the user picks one value from a list and should be able to filter by typing (or the options come from a server).
Select.MultiCombobox.Use @uxf/ui/combobox for a controlled combobox outside a form. Inside a @uxf/form form, use @uxf/form/combobox instead — it registers the field with react-hook-form and derives isInvalid / helperText from validation. This @uxf/ui component is the controlled primitive the form twin wraps.
import { Combobox, ComboboxOption, ComboboxValue } from "@uxf/ui/combobox";
import { useState } from "react";
const options: ComboboxOption[] = [
{ id: "one", label: "Option one" },
{ id: "two", label: "Option two", disabled: true },
{ id: "three", label: "Option three" },
];
function Example() {
const [value, setValue] = useState<ComboboxValue | null>(null);
return (
<Combobox
isClearable
label="Choose an option"
name="example"
onChange={setValue}
options={options}
placeholder="Search..."
value={value}
/>
);
}
Async options — pass loadOptions instead of (or alongside) options; the returned promise supplies the list and local filtering is skipped:
<Combobox
label="Country"
loadOptions={(query, { signal }) => fetchCountries(query, { signal })}
name="country"
onChange={setValue}
placeholder="Search country..."
value={value}
/>
Content below the options — dropdownEndContent, e.g. a button that adds an item the search did not find. The function form gets args.close(); static content leaves the dropdown open:
<Combobox
dropdownEndContent={(args) => (
<Button
isFullWidth
onClick={() => {
args.close();
openAddCountryModal();
}}
variant="text"
>
Add a new country
</Button>
)}
label="Country"
loadOptions={(query, { signal }) => fetchCountries(query, { signal })}
name="country"
onChange={setValue}
value={value}
/>
value is the selected option object (ComboboxValue, i.e. { id, label }) or null, and onChange receives that whole object — not just the id.
Exports: Combobox and the types ComboboxProps, ComboboxOption, ComboboxValue, ComboboxValueId, ComboboxTypeRef.
ComboboxProps<Id = ComboboxValueId, Option = ComboboxOption<Id>, Value = Option> extends FormControlProps<Value | null> and Clearable. ComboboxValueId is number | string; ComboboxValue is { id: Id; label: string }; ComboboxOption adds disabled?: boolean.
| Prop | Type | Default | Description |
|---|---|---|---|
label | ReactNode | — | Required. Field label. |
name | string | — | Required. Field name (also set as data-name). |
value | Value | null | — | Required. Selected option object, or null. |
onChange | (value: Value | null) => void | — | Required. Called with the selected option object (or null). |
options | Option[] | — | Static option list. Optional; omit when using loadOptions. |
loadOptions | (query: string, { signal }) => Promise<Option[]> | — | Async option loader. When set, the component is async and skips local filtering. signal aborts when the result is no longer wanted (new query, unmount, the field paused, a new loadOptions); pass it to fetch. |
placeholder | string | — | Placeholder for the text input. |
helperText | ReactNode | — | Text under the control; styled as an error when isInvalid. |
isClearable | boolean | false | Show a clear button that resets the value to null. |
isDisabled | boolean | false | Disable the control. |
isReadOnly | boolean | false | Prevent changes while staying focusable. |
isInvalid | boolean | false | Invalid styling; sets aria-invalid and styles helperText as an error. |
isRequired | boolean | false | Mark the field required (adds the label indicator). |
hiddenLabel | boolean | false | Visually hide the label (kept for assistive tech). |
renderOption | (option: Option, isSelected: boolean) => ReactNode | option.label | Custom renderer for each dropdown option. |
keyExtractor | (option: Option) => string | number | option.id | Custom React key for options. |
noQueryMessage | string | localized "Start typing to see options" | Shown when the query is empty. |
noOptionsMessage | string | localized "No items found" | Shown when there are no options. |
notFoundMessage | string | "Nic nenalezeno" | Shown when a non-empty query matches nothing. |
iconName | IconName | arrow icon | Icon for the dropdown arrow. |
size | InputGroupSize | "default" | "small", "default", or "large". |
variant | InputGroupVariant | "default" | Input group variant. |
id | string | auto (useId) | Root id; the error-message id derives from it. |
onBlur / onFocus | FocusEventHandler<HTMLInputElement> | — | Focus handlers. |
Layout, dropdown, and ref props: className, style, form, leftAddon, rightAddon, leftElement, rightElement, inputArrow, dropdownClassName, dropdownEndContent, dropdownMatchesInputWidth (default true), dropdownMaxHeight (default 240), dropdownPlacement (default "bottom"), dropdownStrategy, inputRef, inputGroupRef, inputWrapperRef.
options locally by label (accent-insensitive via slugify). With loadOptions, filtering is delegated to the loader (debounced ~200 ms).small, default, large (via size).isInvalid; combine with helperText to show an error message.isDisabled blocks interaction; isReadOnly keeps focus but prevents changes.isClearable, a remove button appears once a value is selected (unless disabled/read-only).dropdownEndContent pins content (typically an "add a new item" button) below the options. It stays visible while the list scrolls and when nothing matches, fits inside dropdownMaxHeight together with the list, and is not part of the arrow-key navigation. Static content (ReactNode) leaves the dropdown open; the function form (args) => ReactNode gets args.close(), which closes the dropdown without changing the value.The component composes the input, label, dropdown, form-component, and icon primitives, so import all of their stylesheets once in your global CSS:
@import url("@uxf/ui/css/icon.css");
@import url("@uxf/ui/css/dropdown.css");
@import url("@uxf/ui/css/label.css");
@import url("@uxf/ui/css/form-component.css");
@import url("@uxf/ui/css/input-basic.css");
@import url("@uxf/ui/css/input.css");
@import url("@uxf/ui/css/combobox.css");
Also requires the global @uxf/ui token layer, set up once per app — see @uxf/ui setup.