Combobox
v0.4.0Searchable single-select combobox with async search, infinite loading, clearable selection, and custom option rendering.
Install
npx shadcn@latest add @signalos/comboboxRequires a configured @signalos registry and a valid SIGNALOS_REGISTRY_TOKEN - get access. Registry dependencies (@signalos/tokens, @signalos/utils, @signalos/button, @signalos/popover, @signalos/command) are pulled automatically.
Preview
Example & code
combobox.example.tsx
// Example: async search with a clearable selection.
import { useState } from "react"
import { Combobox } from "@/components/widgets/combobox/Combobox"
import type { ComboboxOption } from "@/components/widgets/combobox/Combobox.types"
const countries: ComboboxOption[] = [
{ value: "in", label: "India" },
{ value: "us", label: "United States" },
{ value: "de", label: "Germany" },
]
export default function Example() {
const [country, setCountry] = useState("")
const [options, setOptions] = useState(countries)
return (
<Combobox
value={country}
options={options}
clearable
onSearch={(query) =>
setOptions(
countries.filter((option) =>
option.label?.toString().toLowerCase().includes(query.toLowerCase())
)
)
}
onSelect={setCountry}
/>
)
}
Props
ComboboxOption
| Prop | Type | Default | Description |
|---|---|---|---|
| value* | string | - | |
| label* | ReactNode | - | |
| icon | ReactNode | - | |
| disabled | boolean | undefined | - | |
| keywords | string[] | undefined | - |
ComboboxProps
| Prop | Type | Default | Description |
|---|---|---|---|
| options* | ComboboxOption[] | - | |
| value | string | undefined | - | |
| onSearch | ((query: string) => void) | undefined | - | |
| onSelect* | (value: string) => void | - | |
| loading | boolean | undefined | - | |
| disabled | boolean | undefined | - | |
| clearable | boolean | undefined | - | |
| onClear | (() => void) | undefined | - | |
| hasMore | boolean | undefined | - | |
| onLoadMore | (() => void) | undefined | - | |
| placeholder | string | undefined | - | |
| ariaLabel | string | undefined | - | Accessible name for the trigger. Defaults to `placeholder`. The trigger carries `role="combobox"`, and the accessible-name algorithm does not credit that role with a name from its visible text content the way it would a plain button — so without this the control is announced as unlabelled even though sighted users can see the selected value. Set this explicitly when the placeholder is not a meaningful label on its own (e.g. "Select…"). |
| emptyMessage | ReactNode | - | |
| loadMoreLabel | ReactNode | - | |
| renderOption | ((option: ComboboxOption) => ReactNode) | undefined | - | |
| size | ComboboxSize | undefined | - | |
| className | string | undefined | - | |
| contentClassName | string | undefined | - | |
| data-testid | string | undefined | - | Test identifier rendered as `data-testid` on the root element. |
Supporting types
export type ComboboxSize = "sm" | "md" | "lg"
npm dependencies
lucide-react@^1.7.0class-variance-authority@^0.7.1Changelog
combobox
0.4.0
- The trigger now carries an accessible name:
aria-labeldefaults toplaceholder, with a new optionalariaLabelprop to override it. The trigger hasrole="combobox", and the accessible-name algorithm does not credit that role with a name from its visible text the way it would a plain button — so screen readers previously announced the control as unlabelled. The axe assertion in the test suite no longer needs itsbutton-nameoverride.
0.3.0
- The clearable inline "X" button now has
aria-label="Clear selection"and a${dataTestId}-cleartest id, matching every other interactive element in the widget.
0.2.0
- Breaking: rename the
testIdprop todata-testidto match the rest of the registry and the DOM attribute name.
0.1.1
- Content sync fix to bring source in line with published registry payload.
0.1.0
- Initial release: single-select combobox on shadcn/ui
Popover+Command, with async search, infinite loading ("Load more"), clearable selection, disabled options, and custom option rendering.