Combobox

Searchable input that filters a list of options. Built on Base UI Combobox.

const frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"];export function ComboboxBasicDemo() {  return (    <Combobox items={frameworks}>      <ComboboxInput placeholder="Select a framework" className="w-80 max-w-full" />      <ComboboxContent>        <ComboboxEmpty>No items found.</ComboboxEmpty>        <ComboboxList>          {(item) => (            <ComboboxItem key={item} value={item}>              {item}            </ComboboxItem>          )}        </ComboboxList>      </ComboboxContent>    </Combobox>  );}

Usage

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
} from "@reva/ui";

The trigger reuses Reva's Input / InputGroup so it inherits the plain-surface form-field look (no always-on heavy border) and feels like a sibling of Select. Multi-select uses ComboboxChips with ComboboxChip and ComboboxChipsInput instead of ComboboxInput.

ComboboxInput and ComboboxChips both expose a typed size prop ("sm" | "default" | "lg" | "xl") aligned with Input and InputGroup — see Sizes.

Composition

Simple

A single-line input and a flat list (see Basic).

Combobox
├── ComboboxInput
└── ComboboxContent
    ├── ComboboxEmpty
    └── ComboboxList
        ├── ComboboxItem
        └── ComboboxItem

With chips

Multi-select with multiple, chips, and a chips input (see Multiple).

Combobox
├── ComboboxChips
│   ├── ComboboxValue
│   │   └── ComboboxChip
│   └── ComboboxChipsInput
└── ComboboxContent
    ├── ComboboxEmpty
    └── ComboboxList
        ├── ComboboxItem
        └── ComboboxItem

With groups and collection

Nested items per group using ComboboxCollection inside each ComboboxGroup, with a separator between groups (see Groups).

Combobox
├── ComboboxInput
└── ComboboxContent
    ├── ComboboxEmpty
    └── ComboboxList
        ├── ComboboxGroup
        │   ├── ComboboxLabel
        │   └── ComboboxCollection
        │       ├── ComboboxItem
        │       └── ComboboxItem
        ├── ComboboxSeparator
        └── ComboboxGroup
            ├── ComboboxLabel
            └── ComboboxCollection
                ├── ComboboxItem
                └── ComboboxItem

Examples

Each example below is a client component because Base UI's function-children pattern ({(item) => <ComboboxItem … />}) cannot cross the React Server Component boundary directly from MDX. Demo sources live in apps/docs/src/demos/combobox-demos.tsx.

Basic

A simple combobox over a flat string list.

<Combobox items={frameworks}>  <ComboboxInput placeholder="Select a framework" className="w-80 max-w-full" />  <ComboboxContent>    <ComboboxEmpty>No items found.</ComboboxEmpty>    <ComboboxList>      {(item) => (        <ComboboxItem key={item} value={item}>          {item}        </ComboboxItem>      )}    </ComboboxList>  </ComboboxContent></Combobox>

Manual entry (free-text)

Pass freeText to let the field commit a value that isn't in the list. The Combobox stays controlled with a plain string value / onValueChange, and the typed text survives closing the popup. Place a ComboboxCreate row inside ComboboxList (alongside the ComboboxCollection) so it is a real list item — clickable, keyboard-navigable, and committed with Enter. It appears only when the query has no exact match, and shows an Enter hint on the right.

const providers = ["Monzo", "Starling Bank", "HSBC", "Nationwide Building Society", "Vanguard"];export function ComboboxFreeTextDemo() {  const [value, setValue] = React.useState("");  return (    <Combobox freeText value={value} onValueChange={setValue} items={providers}>      <ComboboxInput placeholder="Search or type a provider" className="w-80 max-w-full" showClear />      <ComboboxContent>        <ComboboxList>          <ComboboxCollection>            {(item) => (              <ComboboxItem key={item} value={item}>                {item}              </ComboboxItem>            )}          </ComboboxCollection>          <ComboboxCreate />        </ComboboxList>      </ComboboxContent>    </Combobox>  );}

Inside a Dialog, pass the dialog's content element as container and set positionMethod="fixed" on ComboboxContent, so the popup portals into the dialog's scroll-lock subtree (otherwise the list can't be wheel-scrolled) and escapes the dialog's overflow clip:

const [dialogEl, setDialogEl] = useState<HTMLElement | null>(null);

<DialogContent ref={setDialogEl}>
  <Combobox freeText value={value} onValueChange={setValue} items={items}>
    <ComboboxInput placeholder="Provider" showClear />
    <ComboboxContent container={dialogEl} positionMethod="fixed">
      <ComboboxList>
        <ComboboxCollection>
          {(item) => <ComboboxItem key={item} value={item}>{item}</ComboboxItem>}
        </ComboboxCollection>
        <ComboboxCreate />
      </ComboboxList>
    </ComboboxContent>
  </Combobox>
</DialogContent>

Sizes

Pass size on the Combobox root to scale the trigger and the dropdown items together. The ramp matches Input and InputGroup: sm (32px), default (40px), lg (44px), xl (48px). Items inside ComboboxContent automatically pick up the size from context — no need to pass size to children.

<VStack gap={3}>  {(["sm", "default", "lg", "xl"] as const).map((size) => (    <Combobox key={size} size={size} items={frameworks}>      <ComboboxInput        placeholder={`Size: ${size}`}        className="w-80 max-w-full"      />      <ComboboxContent>        <ComboboxEmpty>No items found.</ComboboxEmpty>        <ComboboxList>          {(item) => (            <ComboboxItem key={item} value={item}>              {item}            </ComboboxItem>          )}        </ComboboxList>      </ComboboxContent>    </Combobox>  ))}</VStack>

Multiple

Pass multiple and render the selection as chips inside ComboboxChips. useComboboxAnchor anchors the popover to the chips wrapper so the dropdown grows to its width.

function ComboboxMultipleDemo() {  const anchor = useComboboxAnchor();  return (    <Combobox      multiple      autoHighlight      items={frameworks}      defaultValue={[frameworks[0]]}    >      <ComboboxChips ref={anchor} className="w-80 max-w-full">        <ComboboxValue>          {(values) => (            <>              {values.map((value) => (                <ComboboxChip key={value}>{value}</ComboboxChip>              ))}              <ComboboxChipsInput placeholder="Add framework" />            </>          )}        </ComboboxValue>      </ComboboxChips>      <ComboboxContent anchor={anchor}>        <ComboboxEmpty>No items found.</ComboboxEmpty>        <ComboboxList>          {(item) => (            <ComboboxItem key={item} value={item}>              {item}            </ComboboxItem>          )}        </ComboboxList>      </ComboboxContent>    </Combobox>  );}

Clear Button

Use the showClear prop on ComboboxInput to render a clear button inside the trigger when the field has a value.

<Combobox items={frameworks} defaultValue={frameworks[0]}>  <ComboboxInput placeholder="Select a framework" showClear className="w-80 max-w-full" />  <ComboboxContent>    <ComboboxEmpty>No items found.</ComboboxEmpty>    <ComboboxList>      {(item) => (        <ComboboxItem key={item} value={item}>          {item}        </ComboboxItem>      )}    </ComboboxList>  </ComboboxContent></Combobox>

Groups

Wrap related items in a ComboboxGroup with a ComboboxLabel and a ComboboxCollection. Pass each group's items to ComboboxGroup items={…} so filtering keeps the group together.

<Combobox items={timezones}>  <ComboboxInput placeholder="Select a timezone" className="w-80 max-w-full" />  <ComboboxContent>    <ComboboxEmpty>No timezones found.</ComboboxEmpty>    <ComboboxList>      {(group) => (        <ComboboxGroup key={group.value} items={group.items}>          <ComboboxLabel>{group.value}</ComboboxLabel>          <ComboboxCollection>            {(item) => (              <ComboboxItem key={item} value={item}>                {item}              </ComboboxItem>            )}          </ComboboxCollection>          <ComboboxSeparator />        </ComboboxGroup>      )}    </ComboboxList>  </ComboboxContent></Combobox>

Custom Items

Render arbitrary content inside ComboboxItem. When items are objects, pass itemToStringValue so the input filter sees a string.

<Combobox  items={countries}  itemToStringValue={(country) => country.label}>  <ComboboxInput placeholder="Search countries…" className="w-80 max-w-full" />  <ComboboxContent>    <ComboboxEmpty>No countries found.</ComboboxEmpty>    <ComboboxList>      {(country) => (        <ComboboxItem key={country.code} value={country}>          <Item size="xs" className="p-0">            <ItemContent>              <ItemTitle className="whitespace-nowrap">{country.label}</ItemTitle>              <ItemDescription>{country.continent} ({country.code})</ItemDescription>            </ItemContent>          </Item>        </ComboboxItem>      )}    </ComboboxList>  </ComboboxContent></Combobox>

Invalid

Set aria-invalid on ComboboxInput for the standalone error look, or wrap in Field with data-invalid to compose label, description, and error copy.

Please select a valid framework.

<VStack gap={4}>  <Combobox items={frameworks}>    <ComboboxInput      placeholder="Select a framework"      aria-invalid="true"      className="w-80 max-w-full"    />    <ComboboxContent>      <ComboboxEmpty>No items found.</ComboboxEmpty>      <ComboboxList>        {(item) => (          <ComboboxItem key={item} value={item}>            {item}          </ComboboxItem>        )}      </ComboboxList>    </ComboboxContent>  </Combobox>  <Field data-invalid>    <FieldLabel htmlFor="combobox-framework-invalid">Framework</FieldLabel>    <Combobox items={frameworks}>      <ComboboxInput        id="combobox-framework-invalid"        placeholder="Select a framework"        aria-invalid        className="w-80 max-w-full"      />      <ComboboxContent>        <ComboboxEmpty>No items found.</ComboboxEmpty>        <ComboboxList>          {(item) => (            <ComboboxItem key={item} value={item}>              {item}            </ComboboxItem>          )}        </ComboboxList>      </ComboboxContent>    </Combobox>    <FieldDescription>Please select a valid framework.</FieldDescription>    <FieldError errors={[{ message: "This field is required." }]} />  </Field></VStack>

Props

Combobox and its subcomponents forward props to Base UI Combobox. See the Base UI Combobox documentation for the full prop surface (items, value, onValueChange, multiple, filter, mode, autoHighlight, etc.).

PropTypeDefaultDescription
itemsT[]Source list passed to Combobox for filtering.
value / defaultValueT | T[]Controlled vs uncontrolled selection.
onValueChange(value: T | T[]) => voidFires when the selection changes.
multiplebooleanfalseEnables multi-select with chips.
freeTextbooleanfalseOpt-in single-select free-text: value / onValueChange become a plain string and a typed value not in items is committed verbatim. Controlled only.
itemToStringValue(item: T) => stringString accessor for object items so the input filter sees a string.
autoHighlightbooleanfalseHighlights the first match as the user types.
disabledbooleanfalseDisables the control.

ComboboxInput / ComboboxChips

PropTypeDefaultDescription
size"sm" | "default" | "lg" | "xl""default"Control scale (32 / 40 / 44 / 48px). Aligned with Input and InputGroup.
showTriggerbooleantrueShow the chevron trigger button (ComboboxInput only).
showClearbooleanfalseShow a clear button when the value is non-empty (ComboboxInput only).
aria-invalidboolean | "true"Renders the error border / focus ring.

ComboboxContent

PropTypeDefaultDescription
containerHTMLElement | nullPortal target for the popup. Inside a Dialog, pass the dialog content node so the list sits in the dialog's scroll-lock subtree and stays wheel-scrollable.
positionMethod"absolute" | "fixed""absolute"Use "fixed" inside a Dialog so the popup escapes the dialog's overflow clip.

ComboboxCreate

The explicit free-text create row (use with freeText). Place it inside ComboboxList, next to the ComboboxCollection, so it joins the keyboard-navigable list — clicking it or pressing Enter commits the typed value. Renders only when the query is non-empty and has no exact match, with a trailing Kbd "Enter". Accepts a render-function child (query: string) => ReactNode (default Use "<query>") and an optional icon.

On this page