Version 0.16.0

Combobox

A text input paired with a filterable dropdown for selecting or typing a value.

Usage#

import { Combobox } from '@backstage/ui';

<Combobox
  name="font"
  label="Font Family"
  options={[
    { id: 'sans', label: 'Sans-serif' },
    { id: 'serif', label: 'Serif' },
    { id: 'mono', label: 'Monospace' },
    { id: 'cursive', label: 'Cursive' },
  ]}
/>

Provide a visible label whenever possible, or use aria-label or aria-labelledby when the field is labeled elsewhere.

Collections#

Combobox accepts one collection source at a time:

  • Pass an options array for standard text rows with optional descriptions, icons, or sections.
  • Pass items with a child render function for domain objects or custom rows.
  • Compose item children directly for a short, fixed collection.
  • Pass a useAsyncList result to options or items for asynchronous data and incremental loading.

The prop types prevent mixing collection sources and incompatible search or loading configurations.

Every option or item passed through options or items needs a stable id. Dynamic render functions receive the full source item, and React Aria supplies its identity to the rendered item component. Statically composed items may provide their own id.

Use ComboboxItemText and ComboboxItemProfile for standard rich rows. Use the low-level ComboboxItem for fully custom content, and provide its textValue for filtering and accessibility.

Option sections are supported only in static options arrays.

Search and selection#

Combobox filters loaded items with a client-side contains match by default. Pass search.filter to match against the full option or item. Custom filters require an options or items source; static composition uses the default text-value match. Pass search={{ mode: 'server' }} with a useAsyncList source to forward the input query to the source instead. To control a client query, pass search.inputValue together with search.onInputChange. Use search.defaultInputValue to set an initial uncontrolled query.

Regular collections use item keys for value, defaultValue, and onChange. Direct async server collections use the full option or item instead. This lets the Combobox retain the selected item while the current server results change. Custom async server items require a canonical textValue; plain options use their label.

While the user edits a direct async server Combobox, the selected item remains committed. Leaving the field restores its canonical text and queries the source with that text. With allowsCustomValue, unmatched text remains in the input and no option is selected.

If server data is managed elsewhere, control search.inputValue and search.onInputChange yourself. This manual mode keeps key-based selection. Pass loading to report loading state and optionally load more results. In server mode, direct useAsyncList sources provide their own query and loading state and cannot be combined with these manual props.

API reference#

PropTypeDefaultDescription
options
(Option | OptionSection)[]AsyncListSource<IdentifiedOption>
-Options to display in the dropdown. Pass Option objects directly, or OptionSection objects to render grouped options under section headings. Pass a useAsyncList result directly for flat async options.
items
Iterable<T>AsyncListSource<T>
-Domain objects to render with a child function. Every item must have an id.
children
ReactElementReactElement[](item: T) => ReactElement
-Static item components, or a render function used together with items.
dependencies
ReadonlyArray<unknown>
-Values outside each item that invalidate cached dynamic item rendering.
search
trueComboboxSearch<T>ComboboxAsyncSearch<T>
-Configures client or server search behavior. Omit it to filter loaded items with the default contains match.
loading
{ state: LoadingState; onLoadMore?: () => void }
-Manual loading state for non-async collections. Async sources provide this automatically.
allowsCustomValue
boolean
falseAllows unmatched text to remain in the input when committed. No option is selected for the custom text.
value
KeyTnull
-Controlled selection. Regular collections use an item key; direct async server collections use the full selected item.
defaultValue
KeyTnull
-Initial uncontrolled selection. Regular collections use an item key; direct async server collections use the full selected item.
onChange
(value: Key | null) => void(item: T | null) => void
-Called when selection changes. Direct async server collections emit the full selected item.
selectedKeydeprecated
Key
-Deprecated compatibility alias for value.
defaultSelectedKeydeprecated
Key
-Deprecated compatibility alias for defaultValue.
onSelectionChangedeprecated
(key: Key | null) => void
-Deprecated compatibility alias for onChange.
inputValuedeprecated
string
-Deprecated compatibility prop for static option arrays. Use search.inputValue instead.
defaultInputValuedeprecated
string
-Deprecated compatibility prop for static option arrays. Use search.defaultInputValue instead.
onInputChangedeprecated
(value: string) => void
-Deprecated compatibility prop for static option arrays. Use search.onInputChange instead.
defaultFilterdeprecated
(textValue: string, inputValue: string) => boolean
-Deprecated compatibility prop for static option arrays. Use search.filter instead.
label
string
-Visible label above the combobox.
secondaryLabel
string
-Secondary text shown next to the label. If not provided and isRequired is true, displays Required.
description
string
-Helper text displayed below the label.
placeholder
string
-Text shown when the input is empty.
size
smallmedium
smallVisual size of the combobox field.
icon
ReactNode
-Icon displayed before the input.
onOpenChange
(isOpen: boolean) => void
-Called when the dropdown opens or closes.
isOpen
boolean
-Controlled open state. Use with onOpenChange.
defaultOpen
boolean
-Initial open state for uncontrolled usage.
isDisabled
boolean
-Prevents user interaction when true.
disabledKeys
Iterable<Key>
-Keys of options that should be disabled.
isRequired
boolean
-Marks the field as required for form validation.
isInvalid
boolean
-Displays the combobox in an error state.
name
string
-Form field name for form submission.
className
string
-Additional CSS class name for custom styling.
style
CSSProperties
-Inline CSS styles object.

Inherits all React Aria ComboBox props.

Option types#

The options prop accepts an array containing either of the following shapes. Use id for new code. The deprecated value identity remains supported for static arrays.

Option

PropTypeDefaultDescription
id
string
-Preferred unique identity for the option. Required for new option features and async option sources.
valuedeprecated
string
-Deprecated compatibility identity for static option arrays. Use id instead.
label
string
-Display text for the option.
disabled
boolean
-Whether the option is disabled.
description
string
-Secondary text displayed below the option label. Requires id instead of the deprecated value field.
leadingIcon
ReactNode
-Icon displayed before the option text. Requires id instead of the deprecated value field.

OptionSection

PropTypeDefaultDescription
title
string
-Heading displayed above the grouped options.
options
Option[]
-Options nested inside the section.

ComboboxItem#

Low-level item wrapper for fully custom item content.

PropTypeDefaultDescription
id
Key
-Item identity for static composition. Dynamic items receive the id from their source item.
textValue
string
-Plain text used for filtering, keyboard navigation, and accessibility.
children
ReactNode(values: ListBoxItemRenderProps) => ReactNode
-Custom item content. Use the render function to respond to item state.
showSelectionIndicator
boolean
falseUses the standard BUI selection indicator and item content layout.
isDisabled
boolean
falseWhether the item is disabled.
className
string
-Additional CSS class name for custom styling.

Inherits all React Aria ListBoxItem props.

ComboboxItemText#

Preset item with a title, optional description, and optional leading icon.

PropTypeDefaultDescription
id
Key
-Item identity for static composition. Dynamic items receive the id from their source item.
title
string
-Primary item text.
description
string
-Secondary text displayed below the title.
leadingIcon
ReactNode
-Icon displayed before the item text.
isDisabled
boolean
falseWhether the item is disabled.
className
string
-Additional CSS class name for custom styling.

Inherits all React Aria ListBoxItem props.

ComboboxItemProfile#

Preset item with an avatar and profile name.

PropTypeDefaultDescription
id
Key
-Item identity for static composition. Dynamic items receive the id from their source item.
name
string
-Profile name.
src
string
-Avatar image source. The avatar displays initials when omitted.
isDisabled
boolean
falseWhether the item is disabled.
className
string
-Additional CSS class name for custom styling.

Inherits all React Aria ListBoxItem props.

Examples#

Label and description#

<Combobox
  name="font"
  label="Font Family"
  description="Choose a font family for your document"
  options={[ ... ]}
/>
Choose a font family for your document

Pass search.filter to replace the default contains match.

<Combobox
  label="Owner"
  options={ownerOptions}
  search={{
    filter: (option, query) =>
      option.label.toLocaleLowerCase().startsWith(query.toLocaleLowerCase()),
  }}
/>

Custom items#

Use item presets or low-level items for fixed rich rows, or pass domain objects through items with a child render function. Pass dependencies when that render function also depends on values outside each item. Presets include the standard BUI selection indicator. Low-level items own their internal layout and selection treatment; set showSelectionIndicator to use the standard indicator and content layout.

import { Combobox, ComboboxItemProfile } from '@backstage/ui';

<Combobox label="Owner" items={owners}>
  {owner => (
    <ComboboxItemProfile
      name={owner.name}
      src={owner.avatarUrl}
    />
  )}
</Combobox>

Server search and loading#

Import useAsyncList from @backstage/ui and pass its result directly to options or items. The Combobox derives query state, loading state, and incremental loading from the list.

Direct async server collections use the full selected option or item for value, defaultValue, and onChange. Custom items need an id and a canonical textValue; plain options use their label. The selected item remains committed while the user edits the query. Leaving the field restores its canonical text unless allowsCustomValue keeps unmatched text.

import { useState } from 'react';
import { Combobox, ComboboxItemProfile, useAsyncList } from '@backstage/ui';

type Owner = {
  id: string;
  textValue: string;
  name: string;
  avatarUrl?: string;
};

const owners = useAsyncList<Owner>({
  async load({ signal, filterText, cursor }) {
    return fetchOwners({ signal, query: filterText, cursor });
  },
});
const [owner, setOwner] = useState<Owner | null>(null);

<Combobox
  label="Owner"
  items={owners}
  placeholder="Search or select an owner"
  search={{ mode: 'server' }}
  value={owner}
  onChange={setOwner}
>
  {owner => <ComboboxItemProfile name={owner.name} src={owner.avatarUrl} />}
</Combobox>

If data fetching is managed elsewhere, control the server query through search and pass loading with the current state and optional load-more callback. This manual mode keeps the regular key-based selection API.

<Combobox
  label="Owner"
  options={results}
  search={{
    mode: 'server',
    inputValue: query,
    onInputChange: setQuery,
  }}
  loading={{ state: isLoading ? 'filtering' : 'idle', onLoadMore }}
/>

Sizes#

<Flex>
  <Combobox
    size="small"
    label="Font family"
    options={[ ... ]}
  />
  <Combobox
    size="medium"
    label="Font family"
    options={[ ... ]}
  />
</Flex>

With icon#

<Combobox
  name="font"
  label="Font Family"
  icon={<RiCloudLine />}
  options={[ ... ]}
/>

Disabled#

<Combobox
  isDisabled
  label="Font family"
  options={[ ... ]}
/>

Disabled options#

<Combobox
  name="font"
  label="Font Family"
  placeholder="Pick a font"
  disabledKeys={['cursive', 'serif']}
  options={[
    { id: 'sans', label: 'Sans-serif' },
    { id: 'serif', label: 'Serif' },
    { id: 'mono', label: 'Monospace' },
    { id: 'cursive', label: 'Cursive' },
  ]}
/>

Custom values#

Set allowsCustomValue to keep text that does not match an option. Custom text does not create a selected option.

<Combobox
  name="country"
  label="Country"
  allowsCustomValue
  placeholder="Type any country"
  options={[
    { id: 'us', label: 'United States' },
    { id: 'ca', label: 'Canada' },
    { id: 'uk', label: 'United Kingdom' },
    { id: 'fr', label: 'France' },
    { id: 'de', label: 'Germany' },
    // ... more options
  ]}
/>

With sections#

Group options under section headings by passing objects with a title and a nested options array.

<Combobox
  name="font"
  label="Font Family"
  options={[
    {
      title: 'Serif Fonts',
      options: [
        { id: 'times', label: 'Times New Roman' },
        { id: 'georgia', label: 'Georgia' },
        { id: 'garamond', label: 'Garamond' },
      ],
    },
    {
      title: 'Sans-Serif Fonts',
      options: [
        { id: 'arial', label: 'Arial' },
        { id: 'helvetica', label: 'Helvetica' },
        { id: 'verdana', label: 'Verdana' },
      ],
    },
  ]}
/>

Responsive#

Size can change at different breakpoints.

<Combobox
  size={{ initial: 'small', lg: 'medium' }}
  label="Font family"
  options={[ ... ]}
/>

Theming#

Our theming system is based on a mix between CSS classes, CSS variables and data attributes. If you want to customise this component, you can use one of these class names below.

  • bui-Combobox
  • bui-ComboboxPopover

Changelog#

Version 0.16.0#

Breaking Changes

  • Breaking Combobox now supports async collections, incremental loading, client and server search, and rich or custom item rendering. Loading placeholders expose .bui-ComboboxLoading and .bui-ComboboxLoadingRow, and stale visible results expose data-stale on .bui-ComboboxList.

    The public ComboboxProps interface is now a union type. #34489

    Migration Guide:

    Required on upgrade:

    Replace interfaces that extend ComboboxProps with type intersections.

    - interface MyComboboxProps extends ComboboxProps {
    -   trackingId: string;
    - }
    + type MyComboboxProps = ComboboxProps & {
    +   trackingId: string;
    + };
    

    Optional migration away from deprecated APIs:

    Prefer id instead of value for plain options. Existing array-valued options using value remain supported as a deprecated compatibility path, but new option content fields and async option sources require id.

    Move input state and custom filtering into the nested search configuration:

    - <Combobox inputValue={query} onInputChange={setQuery} />
    + <Combobox search={{ inputValue: query, onInputChange: setQuery }} />
    

    The existing top-level input state props remain supported as a deprecated compatibility path for plain-array options.

Changes

  • Fixed async pagination in Combobox and Select popovers so additional pages load as users scroll instead of loading every page immediately. Combobox now uses .bui-PopoverContent as its scroll container, while all Select variants use the new .bui-SelectResults results container.

    Searchable Select keeps its search field fixed while results scroll. The new public classes .bui-SelectContent and .bui-SelectResults expose this layout for theme customization. #34591

  • Fixed Combobox client search crashing when used with plain options. #34590

Version 0.15.0#

Changes

  • Added a new Combobox component. It pairs a text input with a filterable dropdown of options and supports single selection, sectioned options, icons, sizes, and custom typed values via allowsCustomValue. #34118