FSAI Design System
Components

Autocomplete Select

Search-first selection field built on Picker for large or grouped option sets.

Overview

AutocompleteSelect is a purpose-built searchable selection field built on top of Picker.

Use it when the main interaction is typing to filter or discover options. It is especially useful when:

  • the option set is large
  • the list may be grouped
  • users should be able to add a new option from the current query

Compared with Select, AutocompleteSelect is more search-centric and more flexible, but also more complex.

Layering

ComponentRole
PickerBase primitive
SelectStandard choose-from-list field
AutocompleteSelectSearch-first field with grouped-option support

Basic Usage

<AutocompleteSelect
  selected={field.value}
  onSelect={field.onChange}
  onBlur={field.onBlur}
  label="Owning Franchisee Entity"
  placeholder="Select a franchisee entity"
  labelIcon="Bank"
  onListEndReached={
    hasNextPage && !isFetchingNextPage ? fetchNextPage : undefined
  }
  options={
    franchiseeGroups?.map((entity) => ({
      value: entity.id,
      label: entity.name,
    })) || []
  }
/>

This mirrors the franchisee entity field in CreateLocationModal.

Another Standard Field Pattern

The same screen also uses AutocompleteSelect for a simpler local option set:

<AutocompleteSelect
  selected={field.value}
  onSelect={field.onChange}
  onBlur={field.onBlur}
  label="Deal Zone"
  labelIcon="LocationPinArea"
  placeholder="Select a deal zone"
  options={
    territories?.map((territory) => ({
      value: territory.id,
      label: territory.name || '',
    })) || []
  }
/>

Use this pattern when the interaction is still search-first, but the option list is already present locally.

Grouped Options

options can also be a grouped record when that improves scanning and discovery.

<AutocompleteSelect
  label="Dependency"
  options={{
    Tasks: taskOptions,
    Campaigns: campaignOptions,
    Automations: automationOptions,
  }}
  selected={selectedDependency}
  onSelect={setSelectedDependency}
/>

Search-Driven Data Updates

Use onChangeSearchText when the backing list should respond to the typed query.

This does not mean AutocompleteSelect should be preferred just because the data source is async. The currently selected option still needs to exist in options, even before the user types, otherwise the closed field can fail to render the current selection correctly.

Treat onChangeSearchText as a search signal. If results are updated from the server, preserve selected and preloaded options in the option set.

<AutocompleteSelect
  label="Course"
  options={courseOptions}
  selected={selectedCourse}
  onSelect={setSelectedCourse}
  onChangeSearchText={setSearch}
  isLoading={isLoadingCourses}
  placeholder="Search courses"
/>

Add New Option

<AutocompleteSelect
  label="Tag"
  options={tagOptions}
  selected={selectedTag}
  onSelect={setSelectedTag}
  onAddNewOption={(query) => createTag(query)}
/>

When the current query does not exactly match an existing option label, an add-new row can appear at the bottom.

Props

PropTypeDefaultDescription
selectedT | nullCurrent selected value.
onSelect(value: T | null) => void | Promise<void>Called when a value is selected. May receive null.
optionsAutocompleteSelectOption[] | Record<string, AutocompleteSelectOption[]>Required. Flat or grouped option data.
labelstringField label.
idstringInput id.
onBlur() => voidBlur handler for form integration.
classNamestringWrapper class name.
disabledbooleanfalseDisables interaction.
requiredbooleanfalseAdds required asterisk in the label.
size'md' | 'sm''md'Trigger/input size.
IconComponentTypeOptional leading trigger icon.
rightAdornmentIconName'ChevronBottom'Right-side trigger icon.
placement'bottom-start' | 'bottom-end' | 'top-start' | 'top-end''bottom-start'Dropdown placement.
offsetnumber6Trigger/content spacing.
placeholderstring'Select'Placeholder text.
errorstring | nullField error text.
onAddNewOption(query: string) => voidCalled when the add-new row is chosen.
onListEndReached() => voidCalled when the bottom sentinel becomes visible.
onChangeSearchText(value: string) => voidCalled whenever search text changes.
emptyStateTitlestringEmpty state title.
emptyStateDescriptionstringEmpty state description.
emptyStateAction{ label, onClick }Optional empty state action button.
buttonClassNamestringAdditional trigger/input class name.
labelIconIconNameSmall icon next to the label.
styleVariant'outline' | 'filled''outline'Trigger/input styling.
isLoadingbooleanfalseShows loading spinner in the content panel.

Option Shape

Option propTypeDescription
valueTRequired picker value.
labelstringRequired display label used for search matching.
disabledbooleanDisables selection.
markedbooleanAdds a visual marked dot indicator.
iconIconNameLeft icon for the option row.
avataravatar subsetAvatar rendering for person/entity choices.
render() => ReactNodeCustom option rendering.

How AutocompleteSelect Uses Picker

Internally, AutocompleteSelect is a more advanced picker composition:

  • closed state: Picker.Trigger wraps a custom button
  • open state: Picker.Input becomes the editable trigger
  • content: Picker.Content
  • grouped headings: Picker.Content.Heading
  • options: Picker.Item
  • empty state: Picker.Empty

It also sets focusStrategy="none" on Picker because focus is managed by the autocomplete input itself.

Select Vs AutocompleteSelect

Use casePrefer
small/medium standard dropdown listSelect
optional in-menu search over a local listSelect searchable
type-to-filter is the primary interactionAutocompleteSelect
grouped optionsAutocompleteSelect
async data by itselfneither by default; keep selected options present first
add-new from current queryAutocompleteSelect

Brand Dashboard Usage

Representative usages:

PatternPathNotes
Paginated field autocompletefsai/apps/brand-dashboard/src/modules/locations/components/CreateLocationModal/CreateLocationModal.tsxFranchisee entity field with onListEndReached.
Local option autocompletefsai/apps/brand-dashboard/src/modules/locations/components/CreateLocationModal/CreateLocationModal.tsxDeal zone field using the same field shell with local options.
Search-first assignment workflowfsai/apps/brand-dashboard/src/components/AssigneeSearchDropdown/AssigneeSearchDropdown.tsxSearch-driven single selection outside a standard form.

Important Cautions

  • Object values must expose a stable id.
  • Search text is cleared when the picker closes.
  • onChangeSearchText should expect that reset behavior.
  • Do not prefer AutocompleteSelect just because the backing data is async.
  • Keep selected and preloaded options available in options even when search results are refreshed.
  • This is not a multi-select component; it is optimized for single search-driven selection.

Guidelines

  • Do use AutocompleteSelect when users need to search rather than scan
  • Do use grouped options when it improves discoverability
  • Do use onChangeSearchText for search-driven filtering while keeping selected options present
  • Do use onAddNewOption when creating from the typed query is part of the flow
  • Don't prefer AutocompleteSelect just because the data source is async
  • Don't use AutocompleteSelect for tiny fixed lists that would be simpler as Select
  • Don't use raw Picker when AutocompleteSelect already matches the search-first pattern

On this page