AutocompleteInput

Form control that tets users choose a value in a list using a dropdown with autocompletion. This input allows editing record fields that are scalar values, e.g. 123, 'admin', etc.

Installation

pnpm dlx shadcn@latest add @shadmin/autocomplete-input

Usage

In addition to the source, <AutocompleteInput> requires one prop: the choices listing the possible values.

import { AutocompleteInput } from "@/components/admin";
 
<AutocompleteInput
  source="category"
  choices={[
    { id: "tech", name: "Tech" },
    { id: "lifestyle", name: "Lifestyle" },
    { id: "people", name: "People" },
  ]}
/>;

By default, the possible choices are built from the choices prop, using:

  • the id field as the option value,
  • the name field as the option text

The form value for the source must be the selected value, e.g.

{
    id: 123,
    title: 'Lorem Ipsum',
    category: 'lifestyle',
}

Props

PropRequiredTypeDefaultDescription
sourceRequired*string-Field name (inferred in ReferenceInput)
choicesRequired*Object[]-List of items to autosuggest. Required if not inside a ReferenceInput.
classNameOptionalstring-The class name to apply to the root element
clearOnBlurOptionalbooleanfalseIf true, clears the search text in the input when the input loses focus
createOptionalElement-A React Element to render when users want to create a new choice
createItemLabelOptionalstring | (filter: string) => ReactNodera.action .create_itemThe label for the menu item allowing users to create a new choice. Used when the filter is not empty.
createLabelOptionalstring | ReactNode-The label used as hint to let users know they can create a new choice. Displayed when the filter is empty.
debounceOptionalnumber250The delay to wait before calling the setFilter function injected when used in a ReferenceInput.
defaultValueOptionalany''The default value for the input
disabledOptionalbooleanfalseIf true, the input is disabled
disableValueOptionalstringdisabledField marking disabled choices
emptyTextOptionalstring''The text to use for the empty element. When non-empty, prepends a "(none)" choice for non-required fields.
emptyValueOptionalstring | number''The underlying form value stored when the user clears the selection (via emptyText or by re-clicking the current choice)
filterToQueryOptionalstring => Objectq => ({ q })How to transform the searchText into a parameter for the data provider
handleHomeEndKeysOptionalbooleanfalseIf true, pressing Home scrolls the dropdown list to the first item and End scrolls to the last item
formatOptionalFunction-Callback taking the value from the form state, and returning the input value.
helperTextOptionalstring | ReactNode-The helper text to display below the input
inputTextOptionalFunction-Required if optionText is a custom Component, this function must return the text displayed for the current selection.
isPendingOptionalbooleanfalseIf true, the component will display a loading indicator.
isOptionEqualToValueOptional(option, value) => booleanareIdsEqualCustom equality check between a choice value and the current field value. Useful when IDs are compared across types (e.g. number vs string).
labelOptionalstring | ReactNode-The label to display for the input
limitChoicesToValueOptionalbooleanfalseIf true, the dropdown shows only the choice that matches the current value instead of all choices
matchSuggestionOptionalFunction-Required if optionText is a React element. Function returning a boolean indicating whether a choice matches the filter. (filter, choice) => boolean
modalOptionalbooleanfalseIf true, the popover will be displayed as a modal
noOptionsTextOptionalReactNodera.navigation.no_resultsContent shown inside the dropdown when no choices match the current filter text
offlineOptionalReactNode-What to render when there is no network connectivity when fetching the choices
onChangeOptionalFunction-A function called with the new value, along with the selected record, when the input value changes
onCreateOptionalFunction-A function called with the current filter value when users choose to create a new choice.
openOnFocusOptionalbooleanfalseIf true, the dropdown opens automatically when the search input receives focus
optionTextOptionalstring | Function | Componentundefined | record RepresentationField name of record to display in the suggestion item or function using the choice object as argument
optionValueOptionalstringidField name of record containing the value to use as input value
parseOptionalFunction-Callback taking the value from the input, and returning the value to be stored in the form state.
selectOnFocusOptionalbooleanfalseIf true, all text already in the search input is selected when the input receives focus
setFilterOptionalFunctionnullA callback to inform the searchText has changed and new choices can be retrieved based on this searchText. Signature searchText => void. This function is automatically set up when using ReferenceInput.
shouldRenderSuggestionsOptionalFunction() => trueA function that returns a boolean to determine whether or not suggestions are rendered.
suggestionLimitOptionalnumbernullLimits the numbers of suggestions that are shown in the dropdown list
translateChoiceOptionalbooleantrueWhether the choices should be translated
validateOptionalFunction | Function[]-An array of validation functions or a single validation function

* source and choices are optional inside <ReferenceInput>.

Defining Choices

The list of choices must be an array of objects with at least two fields: one to use for the name, and the other to use for the value. By default, <AutocompleteInput> will use the id and name fields.

const choices = [
  { id: "tech", name: "Tech" },
  { id: "lifestyle", name: "Lifestyle" },
  { id: "people", name: "People" },
];
<AutocompleteInput source="category" choices={choices} />;

If the choices have different keys, you can use optionText and optionValue to specify which fields to use for the name and value.

const choices = [
  { _id: "tech", label: "Tech" },
  { _id: "lifestyle", label: "Lifestyle" },
  { _id: "people", label: "People" },
];
 
<AutocompleteInput
  source="category"
  choices={choices}
  optionText="label"
  optionValue="_id"
/>;

The choices are translated by default, so you can use translation identifiers as choices:

const choices = [
  { id: "tech", name: "myroot.categories.tech" },
  { id: "lifestyle", name: "myroot.categories.lifestyle" },
  { id: "people", name: "myroot.categories.people" },
];

You can opt-out of this translation by setting the translateChoice prop to false.

If you need to fetch the options from another resource, you're usually editing a many-to-one or a one-to-one relationship. In this case, wrap the <AutocompleteInput> in a <ReferenceInput>. You don't need to specify the source and choices prop in this case - the parent component injects them based on the possible values of the related resource.

<ReferenceInput label="Author" source="author_id" reference="authors">
  <AutocompleteInput />
</ReferenceInput>

You can also pass an array of strings for the choices:

const categories = ["tech", "lifestyle", "people"];
<AutocompleteInput source="category" choices={categories} />;
// is equivalent to
const choices = categories.map((value) => ({ id: value, name: value }));
<AutocompleteInput source="category" choices={choices} />;

Using Inside <ReferenceInput>

When used inside a <ReferenceInput>, whenever users type a string in the autocomplete input, <AutocompleteInput> calls dataProvider.getList() using the string as filter, to return a filtered list of possible options from the reference resource. This filter is built using the filterToQuery prop.

By default, the filter is built using the q parameter. This means that if the user types the string 'lorem', the filter will be { q: 'lorem' }.

You can customize the filter by setting the filterToQuery prop. It should be a function that returns a filter object.

const filterToQuery = (searchText) => ({ name_ilike: `%${searchText}%` });
 
<ReferenceInput label="Author" source="author_id" reference="authors">
  <AutocompleteInput filterToQuery={filterToQuery} />
</ReferenceInput>;

Using Inside Another Modal

When used inside another modal (e.g. a <Dialog>), the popover containing the suggestions may struggle with some pointer events like scrolling. To fix this, set the modal prop to true to render the popover as a modal.

import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { AutocompleteInput, Form } from "@/components/admin";
 
const MyDialog = () => (
  <Dialog open>
    <DialogContent>
      <DialogHeader>
        <DialogTitle>My dialog</DialogTitle>
      </DialogHeader>
      <Form>
        <AutocompleteInput
          source="category"
          choices={[
            { id: "tech", name: "Tech" },
            { id: "lifestyle", name: "Lifestyle" },
            { id: "people", name: "People" },
          ]}
          modal
        />
      </Form>
    </DialogContent>
  </Dialog>
);

Using A Custom Element For Options

You can pass a custom element as optionText to have <AutocompleteInput> render each suggestion in a custom way.

<AutocompleteInput> will render the custom option element inside a <RecordContext>, using the related choice as the record prop. You can use Field components there.

However, as the underlying <Autocomplete> component requires that the current selection is a string, you must also pass a function as the inputText prop. This function should return a text representation of the current selection. You should also pass a matchSuggestion function to filter the choices based on the current selection.

const choices = [
  { id: 123, first_name: "Leo", last_name: "Tolstoi", avatar: "/penguin" },
  { id: 456, first_name: "Jane", last_name: "Austen", avatar: "/panda" },
];
const OptionRenderer = () => {
  const record = useRecordContext();
  return (
    <span>
      <img src={record.avatar} />
      {record.first_name} {record.last_name}
    </span>
  );
};
 
const optionText = <OptionRenderer />;
const inputText = (choice) => `${choice.first_name} ${choice.last_name}`;
const matchSuggestion = (filter, choice) => {
  return (
    choice.first_name.toLowerCase().includes(filter.toLowerCase()) ||
    choice.last_name.toLowerCase().includes(filter.toLowerCase())
  );
};
 
<AutocompleteInput
  source="author_id"
  choices={choices}
  optionText={optionText}
  inputText={inputText}
  matchSuggestion={matchSuggestion}
/>;

Make sure you pass stable references to the functions passed to the inputText and matchSuggestion by either declaring them outside the component render function or by wrapping them in a useCallback.

Make sure you also pass a stable reference to the element passed to the optionText prop by calling it outside the component render function like so:

const OptionRenderer = () => {
  const record = useRecordContext();
  return (
    <span>
      <img src={record.avatar} />
      {record.first_name} {record.last_name}
    </span>
  );
};
 
const optionText = <OptionRenderer />;

Creating New Choices On The Fly

To allow users to add new options, pass a React element as the create prop. <AutocompleteInput> will then render a menu item at the bottom of the list, which will render the passed element when clicked.

import {
  Edit,
  SimpleForm,
  ReferenceInput,
  AutocompleteInput,
  TextInput,
} from "@/components/admin";
import { useCreateSuggestionContext } from "ra-core";
 
const CreateTag = () => {
  const { onCancel, onCreate, filter } = useCreateSuggestionContext();
  const [newTagName, setNewTagName] = React.useState(filter ?? "");
 
  const handleChangeTagName = (event: React.ChangeEvent<HTMLInputElement>) => {
    setNewTagName(event.currentTarget.value);
  };
  const saveTag = () => {
    const newTag = { label: newTagName, id: newTagName.toLowerCase() };
    tags.push(newTag);
    setNewTagName("");
    onCreate(newTag);
  };
  const handleSubmit = (event: React.FormEvent) => {
    event.preventDefault();
    saveTag();
  };
 
  return (
    <Dialog open onOpenChange={onCancel}>
      <DialogContent className="sm:max-w-[425px]">
        <DialogHeader>
          <DialogTitle>Create a tag</DialogTitle>
        </DialogHeader>
        <form onSubmit={handleSubmit} className="space-y-4">
          <div className="space-y-2">
            <Label htmlFor="name">New tag name</Label>
            <Input
              id="name"
              value={newTagName}
              onChange={handleChangeTagName}
              autoFocus
            />
          </div>
        </form>
        <DialogFooter>
          <Button variant="outline" onClick={onCancel}>
            Cancel
          </Button>
          <Button onClick={saveTag}>Save</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
};
 
const BookCreateEdit = () => (
  <Edit>
    <SimpleForm>
      <AutocompleteInput
        source="tag_id"
        choices={tags}
        optionText="label"
        create={<CreateTag />}
        createLabel="Start typing to create a new tag"
        createItemLabel="Create %{item}"
      />
    </SimpleForm>
  </Edit>
);

If you want to customize the label of the "Create XXX" option, use the createItemLabel prop.

If you just need to ask users for a single string to create the new option, you can use the onCreate prop instead.

selectOnFocus

When true, any text already present in the search input is selected when the input receives focus. This lets users immediately start typing a new query without having to manually clear the previous filter text.

<AutocompleteInput source="category" choices={choices} selectOnFocus />

openOnFocus

When true, the dropdown opens automatically as soon as the search input gains focus. This saves the user an extra click when they tab into the field or click the trigger button.

<AutocompleteInput source="category" choices={choices} openOnFocus />

noOptionsText

The content rendered inside the dropdown when no choices match the current filter. Accepts a string or any React node. Defaults to the translation of ra.navigation.no_results ("No matching item found.").

<AutocompleteInput
  source="category"
  choices={choices}
  noOptionsText="Nothing matches your search"
/>

limitChoicesToValue

When true, the dropdown filters the displayed choices to only the one that matches the current field value. All other choices are hidden. This is useful in read-heavy UIs where you want to confirm the selection without presenting the full list.

<AutocompleteInput source="category" choices={choices} limitChoicesToValue />

isOptionEqualToValue

A custom comparator function called with (choiceValue, fieldValue) that returns true when they should be considered equal. By default the component uses areIdsEqual, which does a loose == comparison so string and number IDs match. Override this when you need strict equality or a domain-specific comparison.

<AutocompleteInput
  source="category"
  choices={choices}
  isOptionEqualToValue={(option, value) => option.id === value.id}
/>

handleHomeEndKeys

When true, pressing the Home key while the dropdown is open scrolls the option list to the first item, and pressing End scrolls to the last item. Useful for long lists where keyboard-only navigation is important.

<AutocompleteInput source="category" choices={choices} handleHomeEndKeys />

emptyValue

The form value stored when the selection is cleared — either by selecting the emptyText entry or by re-clicking an already-selected choice on a non-required field. Defaults to "". Use null or 0 when your backend distinguishes an empty selection from an absent field.

<AutocompleteInput
  source="category"
  choices={choices}
  emptyText="No category"
  emptyValue={null}
/>

emptyText

When set, prepends a special "(none)" entry at the top of the dropdown for non-required fields. Selecting this entry sets the field value to emptyValue (defaults to ""). Useful when users need a way to explicitly clear a previously chosen value.

<AutocompleteInput
  source="category"
  choices={choices}
  emptyText="No category"
/>

clearOnBlur

When true, the search text typed in the filter input is cleared when the input loses focus. This means if the user opens the dropdown, types to narrow results, then clicks away without selecting, the filter resets to empty.

<AutocompleteInput source="category" choices={choices} clearOnBlur />