Version 0.16.0

Select

A dropdown for selecting from predefined options with search support.

Font Family

Usage#

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

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

Collections#

Select 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 SelectItemText and SelectItemProfile for standard rich rows. Use the low-level SelectItem for fully custom content, and provide its textValue for search, accessibility, and the selected text shown in the trigger. The presets derive selected text from their title or name.

Option sections are supported only in static options arrays.

Search and loading#

Search is opt-in. Pass search as a shorthand for the default client-side contains filter, or pass an object to configure it. Client search.filter receives the full option or item and the current query. Custom filters require an options or items source. Static composition supports the default text-value filter. To control the query, pass search.inputValue together with search.onInputChange. Use search.defaultInputValue to set an initial uncontrolled query.

For server search, either pass a useAsyncList result with search={{ mode: 'server' }}, or manage the query yourself with controlled search.inputValue and search.onInputChange. Async sources provide loading state and incremental loading automatically, so they cannot be combined with manual loading or controlled server query props. Use loading only when data fetching is managed outside the component.

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
trueSelectSearch<T>SelectAsyncSearch<T>
-Enables the search field and configures client or server search behavior. Client filters receive the full item.
loading
{ state: LoadingState; onLoadMore?: () => void }
-Manual loading state for non-async collections. Async sources provide this automatically.
selectionMode
singlemultiple
singleSingle or multiple selection mode.
value
KeyKey[]null
-Controlled selected keys. Use one key for single selection or an array for multiple selection.
defaultValue
KeyKey[]
-Initial selected keys for uncontrolled usage. Use one key for single selection or an array for multiple selection.
onChange
(key: Key | null) => void(keys: Key[]) => void
-Called when selection changes.
selectedKeydeprecated
Key
-Deprecated compatibility alias for value.
defaultSelectedKeydeprecated
Key
-Deprecated compatibility alias for defaultValue.
onSelectionChangedeprecated
(key: Key | null) => void
-Deprecated compatibility alias for onChange.
label
string
-Visible label above the select.
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
Select an optionText shown when no option is selected.
size
smallmedium
smallVisual size of the select field.
icon
ReactNode
-Icon displayed before the selected value.
searchabledeprecated
boolean
-Deprecated compatibility prop for static option arrays. Use search instead.
searchPlaceholderdeprecated
string
Search...Deprecated compatibility prop for static option arrays. Use search.placeholder instead.
isOpen
boolean
-Controlled open state. Use with onOpenChange.
defaultOpen
boolean
-Initial open state for uncontrolled usage.
onOpenChange
(isOpen: boolean) => void
-Called when the dropdown opens or closes.
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 select 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 Select 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.

SelectItem#

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 search, 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.

SelectItemText#

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.

SelectItemProfile#

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#

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

Sizes#

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

With icon#

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

Disabled#

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

Disabled options#

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

Searchable#

Pass search to use the default contains filter.

<Select
  name="country"
  label="Country"
  search={{ placeholder: 'Search countries...' }}
  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
  ]}
/>
Country
<Select
  label="Owner"
  options={ownerOptions}
  search={{
    placeholder: 'Search owners...',
    filter: (option, query) =>
      option.label.toLocaleLowerCase().startsWith(query.toLocaleLowerCase()),
  }}
/>

Multiple selection#

<Select
  name="options"
  label="Select multiple options"
  selectionMode="multiple"
  options={[
    { id: 'option1', label: 'Option 1' },
    { id: 'option2', label: 'Option 2' },
    { id: 'option3', label: 'Option 3' },
    { id: 'option4', label: 'Option 4' },
  ]}
/>
Select multiple options

Searchable multiple#

Combine search and multiple selection.

<Select
  name="skills"
  label="Skills"
  search={{ placeholder: 'Filter skills...' }}
  selectionMode="multiple"
  options={[
    { id: 'react', label: 'React' },
    { id: 'typescript', label: 'TypeScript' },
    { id: 'javascript', label: 'JavaScript' },
    { id: 'python', label: 'Python' },
    // ... more options
  ]}
/>
Skills

With sections#

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

<Select
  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' },
      ],
    },
  ]}
/>
Font Family

Searchable with sections#

Sections are preserved when filtering with search.

<Select
  name="font"
  label="Font Family"
  search={{ placeholder: 'Search fonts...' }}
  options={[
    {
      title: 'Serif Fonts',
      options: [
        { id: 'times', label: 'Times New Roman' },
        { id: 'georgia', label: 'Georgia' },
      ],
    },
    {
      title: 'Sans-Serif Fonts',
      options: [
        { id: 'arial', label: 'Arial' },
        { id: 'helvetica', label: 'Helvetica' },
      ],
    },
  ]}
/>
Font Family

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 { Select, SelectItem } from '@backstage/ui';

<Select label="Release channel" items={releaseChannels}>
  {channel => (
    <SelectItem textValue={channel.name} showSelectionIndicator>
      <CustomReleaseChannel channel={channel} />
    </SelectItem>
  )}
</Select>

Server search and loading#

Import useAsyncList from @backstage/ui and pass its result directly to options or items. With server search, query state, loading state, and incremental loading are derived from the list. With client search, Select filters the currently loaded items on the client.

import { Select, useAsyncList } from '@backstage/ui';

const owners = useAsyncList({
  async load({ signal, filterText, cursor }) {
    return fetchOwners({ signal, query: filterText, cursor });
  },
});

<Select
  label="Owner"
  options={owners}
  search={{ mode: 'server', placeholder: 'Search owners...' }}
/>

If data fetching is managed elsewhere, control the server query through search and pass loading with the current state and optional load-more callback.

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

Responsive#

Size can change at different breakpoints.

<Select
  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-Select
  • bui-SelectPopover

Changelog#

Version 0.16.0#

Breaking Changes

  • Breaking Select now supports async collections, incremental loading, client and server search, and rich or custom item rendering. Loading placeholders expose .bui-SelectLoading and .bui-SelectLoadingRow, and stale retained results expose data-stale on .bui-SelectList.

    The public SelectProps interface is now a union type, and Select popover list content is no longer a direct child of .bui-SelectPopover. #34489

    Migration Guide:

    Required on upgrade:

    Replace interfaces that extend SelectProps with type intersections.

    - interface MySelectProps extends SelectProps {
    -   trackingId: string;
    - }
    + type MySelectProps = SelectProps & {
    +   trackingId: string;
    + };
    

    Update CSS selectors that rely on list content being a direct child of .bui-SelectPopover. Select popovers now use the standard BUI Popover content structure, with contents wrapped in .bui-Box.bui-PopoverContent. The existing .bui-Popover.bui-SelectPopover root classes are unchanged.

    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.

    Replace searchable and searchPlaceholder with nested search configuration:

    - <Select searchable searchPlaceholder="Search owners" />
    + <Select search={{ placeholder: 'Search owners' }} />
    

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

Version 0.15.0#

Changes

  • Added support for grouping options into sections in the Select component. You can now pass section objects with a title and a nested options array alongside (or instead of) regular options to render grouped dropdowns with section headers. #34012

Version 0.14.0#

Changes

  • Fixed form field descriptions not being connected to inputs via aria-describedby, making them accessible to screen readers. Added a descriptionSlot prop to FieldLabel that uses React Aria's slot mechanism to automatically wire up the connection. #33817

Version 0.13.0#

Changes

  • Migrated all components from useStyles to useDefinition hook. Exported OwnProps types for each component, enabling better type composition for consumers. #33050

  • Fixed focus ring styles to use React Aria's [data-focus-visible] data attribute instead of the native CSS :focus-visible pseudo-class. This ensures keyboard focus rings render reliably when focus is managed programmatically by React Aria (e.g. inside a GridList, Menu, or Select). #33358

  • Fixed scroll overflow in Menu and Select popover content when constrained by viewport height. #33049

  • Fixed focus-visible outline styles for Menu and Select components. #32983

  • The Select trigger now automatically adapts its background colour based on the parent background context. #33102

Version 0.11.0#

Changes

  • Added missing aria-label attributes to SearchField components in Select, MenuAutocomplete, and MenuAutocompleteListbox to fix accessibility warnings. #32337

Version 0.9.0#

Breaking Changes

  • Breaking The SelectProps interface now accepts a generic type parameter for selection mode.

    Added searchable and multiple selection support to Select component. The component now accepts searchable, selectionMode, and searchPlaceholder props to enable filtering and multi-selection modes. #31649

    Migration Guide:

    If you're using SelectProps type directly, update from SelectProps to SelectProps<'single' | 'multiple'>. Component usage remains backward compatible.

Changes

  • Fixed CSS issues in Select component including popover width constraints, focus outline behavior, and overflow handling. #31618