Component

A searchable dropdown combining a text input with a list of options. Supports grouping, custom indicators, and portal rendering.

preview
React
"use client" import Combobox from "@radui/ui/Combobox" const ComboboxExample = () => { return ( <Combobox.Root> <Combobox.Trigger> Select an option </Combobox.Trigger> <Combobox.Portal> <Combobox.Content> <Combobox.Search /> <Combobox.Group> <Combobox.Item value="apple">Apple</Combobox.Item> <Combobox.Item value="banana">Banana</Combobox.Item> <Combobox.Item value="cherry">Cherry</Combobox.Item> <Combobox.Item value="grape">Grape</Combobox.Item> <Combobox.Item value="mango">Mango</Combobox.Item> </Combobox.Group> </Combobox.Content> </Combobox.Portal> </Combobox.Root> ) } export default ComboboxExample

Keep option value and label stable when a Combobox has hundreds of options. This incremental pattern keeps keyboard navigation responsive by mounting more matching rows before focus reaches the list boundary.

React
"use client" import * as React from "react" import { flushSync } from "react-dom" import Combobox from "@radui/ui/Combobox" const OPTIONS = Array.from({ length: 1000 }, (_, index) => ({ value: `item-${index + 1}`, label: `Item ${index + 1}` })) const PAGE_SIZE = 40 const LargeListComboboxExample = () => { const [visibleCount, setVisibleCount] = React.useState(PAGE_SIZE) const [value, setValue] = React.useState("") const [query, setQuery] = React.useState("") const selectedLabel = OPTIONS.find((option) => option.value === value)?.label const filteredOptions = React.useMemo(() => { const normalizedQuery = query.trim().toLowerCase() if (!normalizedQuery) { return OPTIONS } return OPTIONS.filter((option) => ( option.label.toLowerCase().includes(normalizedQuery) )) }, [query]) const visibleOptions = filteredOptions.slice(0, visibleCount) const canShowMore = visibleCount < filteredOptions.length React.useEffect(() => { setVisibleCount(PAGE_SIZE) }, [query]) const showMore = React.useCallback(() => { setVisibleCount((current) => Math.min(current + PAGE_SIZE, filteredOptions.length)) }, [filteredOptions.length]) const showMoreBeforeNavigation = React.useCallback(() => { flushSync(() => { setVisibleCount((current) => Math.min(current + PAGE_SIZE, filteredOptions.length)) }) }, [filteredOptions.length]) return ( <Combobox.Root value={value} onValueChange={setValue}> <Combobox.Trigger> {selectedLabel || "Search items..."} </Combobox.Trigger> <Combobox.Portal> <Combobox.Content style={{ maxHeight: 256, overflowY: "auto" }} onScroll={(event) => { const target = event.currentTarget const remaining = target.scrollHeight - target.scrollTop - target.clientHeight if (remaining < 48 && canShowMore) { showMore() } }} onKeyDownCapture={(event) => { if (!canShowMore || event.key !== "ArrowDown") { return } const activeValue = document.activeElement?.getAttribute("data-value") const lastVisibleValue = visibleOptions[visibleOptions.length - 1]?.value if (activeValue === lastVisibleValue) { showMoreBeforeNavigation() } }} > <Combobox.Search placeholder="Filter items" onInput={(event) => { setQuery(event.currentTarget.value) }} /> <Combobox.Group> {visibleOptions.map((option) => ( <Combobox.Item key={option.value} value={option.value} label={option.label} > {option.label} </Combobox.Item> ))} </Combobox.Group> </Combobox.Content> </Combobox.Portal> </Combobox.Root> ) } export default LargeListComboboxExample

Import all parts of the component and piece them together

React
import Combobox from "@radui/ui/Combobox" export default () => { return ( <Combobox.Root> <Combobox.Trigger /> <Combobox.Content> <Combobox.Search /> <Combobox.Group> <Combobox.Item value="" /> </Combobox.Group> </Combobox.Content> </Combobox.Root> ) }

The root Combobox component.

PropTypeDefault
value
string
--
defaultValue
string
--
open
boolean
--
disabled
boolean
false
onValueChange
function
--
onOpenChange
function
--

A single selectable option in the combobox list.

PropTypeDefault
value*
string
--
disabled
boolean
false
className
string
""
ShortcutDescription
ArrowDown

Moves focus from the input into the popup, or to the next available option.

ArrowUp

Moves focus to the previous option while the popup is open.

Enter

Accepts the highlighted option and updates the combobox value.

Escape

Closes the popup and keeps focus on the combobox input.

Tab

Moves focus out of the combobox using the normal page tab order.

ReferenceWhat Rad UI follows

Follows editable combobox guidance for an input with an associated popup of suggested values.

Uses listbox option semantics for the popup collection and active option state.