molecule

AutocompleteSelect

Generic searchable async select with Popover, debounced search, keyboard navigation, and custom item rendering.

Overview

The AutocompleteSelect component provides a searchable dropdown that fetches options asynchronously. It's generic over the item type T, supports debounced search, keyboard navigation (arrow keys, Enter, Escape), custom item rendering, and composes Popover internally. Use it for patient lookups, entity searches, and any scenario requiring async search-and-select.

import { AutocompleteSelect } from '@enara-health/ui-react';

<AutocompleteSelect
  value={selectedId}
  onChange={setSelectedId}
  fetchItems={searchPatients}
  getItemKey={(p) => p.id}
  getItemLabel={(p) => p.name}
  placeholder="Search patients..."
/>

The matching logic lives in your fetchItems function — the component is agnostic to how you filter. Here are three common strategies:

Starts with

Alphabetical filtering — try "bl"

Contains

Match anywhere in the label — try "an"

Fuzzy

Characters in order — try "bry"

Props

Prop Type Default Description
value string | null - Required. Currently selected item key
onChange (value: string | null) => void - Required. Callback when selection changes
fetchItems (searchTerm: string) => Promise<T[]> - Required. Async function to fetch items based on search term
getItemKey (item: T) => string - Required. Extracts the unique key from an item
getItemLabel (item: T) => string - Required. Extracts the display label from an item
placeholder string 'Select item' Placeholder text when no item is selected
renderItem (item: T) => ReactNode - Custom render function for dropdown items
initialDisplayText string '' Pre-populated display text when a value is already selected on mount
emptyMessage string 'No items found' Message shown when search returns no results
disabled boolean false Disables the component and prevents interaction
className string - Additional CSS class names

Variants

Basic Async Search

The default usage with an async data source.

const searchPatients = async (term: string) => {
  const res = await fetch(`/api/patients?q=${term}`);
  return res.json();
};

<AutocompleteSelect
  value={selectedId}
  onChange={setSelectedId}
  fetchItems={searchPatients}
  getItemKey={(p) => p.id}
  getItemLabel={(p) => p.name}
  placeholder="Search patients..."
/>

Custom Item Rendering

Use renderItem to display rich content in the dropdown options.

<AutocompleteSelect
  value={selectedId}
  onChange={setSelectedId}
  fetchItems={searchUsers}
  getItemKey={(u) => u.id}
  getItemLabel={(u) => u.name}
  renderItem={(u) => (
    <Box display="flex" gap={8} alignItems="center">
      <Avatar src={u.avatar} size="sm" />
      <div>
        <Text weight="medium">{u.name}</Text>
        <Text size="xs" color="muted">{u.email}</Text>
      </div>
    </Box>
  )}
/>

Pre-Selected Value

Use initialDisplayText when a value is already selected on mount (e.g., editing an existing record).

<AutocompleteSelect
  value={patient.practitionerId}
  onChange={handlePractitionerChange}
  fetchItems={searchPractitioners}
  getItemKey={(p) => p.id}
  getItemLabel={(p) => p.name}
  initialDisplayText={patient.practitionerName}
  placeholder="Assign practitioner..."
/>

Disabled State

Prevent interaction when the field should be read-only.

<AutocompleteSelect
  value={selectedId}
  onChange={setSelectedId}
  fetchItems={searchItems}
  getItemKey={(item) => item.id}
  getItemLabel={(item) => item.name}
  disabled
/>

Examples

Patient Lookup

Search and select a patient from the database.

const searchPatients = async (term: string) => {
  const response = await api.get('/patients', { params: { search: term } });
  return response.data;
};

<FormField label="Patient">
  <AutocompleteSelect
    value={patientId}
    onChange={setPatientId}
    fetchItems={searchPatients}
    getItemKey={(p) => p.id}
    getItemLabel={(p) => `${p.firstName} ${p.lastName}`}
    placeholder="Search by name or ID..."
    emptyMessage="No patients found"
  />
</FormField>

In a Form with FormField

Combine with FormField for label and error state support.

<FormField label="Assigned Practitioner" error={errors.practitioner}>
  <AutocompleteSelect
    value={formData.practitionerId}
    onChange={(id) => setFormData(prev => ({ ...prev, practitionerId: id }))}
    fetchItems={searchPractitioners}
    getItemKey={(p) => p.id}
    getItemLabel={(p) => p.name}
    initialDisplayText={formData.practitionerName}
  />
</FormField>

Accessibility

  • Pattern: Implements the combobox pattern with search input and listbox.
  • Keyboard Support: Arrow Up/Down to navigate items, Enter to select, Escape to close, typing triggers search.
  • Focus Management: Focus moves to the search input when the dropdown opens.
  • Screen Reader: Trigger reads as a button with the current selected value or placeholder text.
  • WCAG Compliance: Meets AA standards for interactive form controls.

Best Practices

  • Use for async data sources — for small static lists, use Select instead
  • Provide meaningful placeholder text that describes what to search for
  • Customize emptyMessage to guide users when no results are found
  • Wrap in FormField for label, description, and error support
  • Use renderItem to show additional context (avatar, email, etc.)