Folio

Components

Combobox

A searchable select. It pairs a text input with a dropdown list so the reader can filter by typing, which keeps long option sets manageable.

Combobox is a higher-order component, composed from the Command and Popover primitives. The Popover positions the list; Command supplies the search input, filtering, and keyboard navigation.

Preview

Rendered with live Folio tokens. Open it and type to filter, no dark: classes.

Variants

<Combobox options={regions} />
// Combobox renders one flat list.
// Compose Popover + Command for headings.
<Command>
  <CommandInput placeholder="Search..." />
  <CommandList>
    <CommandGroup heading="Production">
      <CommandItem value="prod-us">
        Production US
      </CommandItem>
    </CommandGroup>
  </CommandList>
</Command>
// Options take no icon. Compose
// Popover + Command for option icons.
<CommandItem value="datasets">
  <Database className="text-muted-foreground" />
  <span className="truncate">Datasets</span>
</CommandItem>
const models = [
  { value: "sol", label: "Sol",
    tag: <Badge variant="secondary" size="sm">New</Badge> },
  { value: "lyra", label: "Lyra", disabled: true },
];
<Combobox options={models} />
<Combobox
  options={regions}
  size="sm"
  icon={<Globe className="size-3.5" />}
/>
<Combobox options={[]} isLoading />

Default, with tags, small with a trigger icon, and loading use the Combobox API. Combobox renders one flat list with no option icons, so the grouped and icon examples compose Popover and Command directly.

States

Default. Resting trigger, nothing selected.

Selected. The trigger shows the chosen label.

Disabled. Dimmed; the list cannot open.

Open. The list drops with the search input focused.

Searching. Typing narrows the list to matches.

Empty. No option matches the query.

Open, searching, and empty are interaction states. The open, searching, and empty cells render the underlying Command list inline so each is visible at rest.

API

Prop
Type
Default
Description
options
ComboboxOption[]
required
The selectable options: { value, label, disabled?, tag? }. A tag renders next to the label. The group field is reserved and not rendered yet.
value
string
undefined
Selected value when the combobox is controlled.
onValueChange
(value: string) => void
undefined
Called with the new value. Picking the selected option again sends an empty string.
noopOnReselect
boolean
false
Keep the value when the selected option is picked again, instead of clearing it.
placeholder
string
"Select an option…"
Trigger text shown when nothing is selected.
searchPlaceholder
string
"Search…"
Placeholder of the search input.
emptyText
string
"No results found."
Text shown when no option matches the search.
disabled
boolean
false
Disables the trigger and blocks opening the list.
size
"default" | "sm"
"default"
Trigger size. Uses the Button sizes of the same name.
className
string
undefined
Classes for the trigger. triggerClassName is an alias.
contentClassName
string
undefined
Classes for the popover panel.
icon
ReactNode
undefined
Leading element in the trigger, before the label.
selectedLabel
string
undefined
Trigger label for a value that is not in options.
onSearchChange
(value: string) => void
undefined
Called when the search text changes. Use it for server-side search.
shouldFilter
boolean
true
Set false to skip client-side filtering, for example when the server filters.
isLoading
boolean
false
Disables the trigger and shows a loading item while options load.
isSearching
boolean
false
Shows an updating row while a new search runs. The trigger stays enabled.
onLoadMore / hasMore
() => void | Promise<void> / boolean
undefined / false
Infinite scroll: onLoadMore runs when the end of the list scrolls into view and hasMore is true.
isLoadingMore
boolean
false
Shows a spinner at the end of the list while the next page loads.
modal
boolean
false
Set true inside a modal Dialog, so the popover keeps focus and blocks outside scroll.

Don't and Do

Don't

Don't reach for a Combobox when the option list is short and fixed. A handful of choices the reader can scan at a glance does not benefit from a search field, and the extra step to type slows them down. Use a Select instead.

Do
<Combobox
  options={timezones}
  placeholder="Select a timezone..."
  onValueChange={setZone}
/>

Use it once the list is long enough that filtering meaningfully reduces the work of finding the right option.

import { Combobox } from "@/components/combobox";

const regions = [
  { value: "us-east", label: "US East (Virginia)" },
  { value: "eu-west", label: "EU West (Ireland)" },
  // ...a long list worth filtering
];

export function RegionPicker() {
  return (
    <Combobox
      options={regions}
      placeholder="Select a region..."
      searchPlaceholder="Search regions..."
    />
  );
}
Folio v1.2 · June 2026