AutocompleteArrayInput

Lets users choose multiple values in a list using a dropdown with autocompletion.

Installation

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

This input allows editing values that are arrays of scalar values, e.g. [123, 456].

Usage

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

import { AutocompleteArrayInput } from "@/components/admin";
 
<AutocompleteArrayInput
  source="tags"
  choices={[
    { id: "u001", name: "Tech" },
    { id: "u002", name: "News" },
    { id: "u003", name: "Lifestyle" },
    { id: "u004", name: "Entertainment" },
    { id: "u005", name: "Sports" },
    { id: "u006", name: "Health" },
    { id: "u007", name: "Education" },
    { id: "u008", name: "Finance" },
    { id: "u009", name: "Travel" },
  ]}
/>;

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 an array of the selected values, e.g.

{
    id: 123,
    title: 'Lorem Ipsum',
    roles: ['u001', 'u003', 'u004'],
}

Props

PropRequiredTypeDefaultDescription
sourceRequired*string-Field name (inferred in ReferenceArrayInput)
choicesRequired*any[]-List of choices
classNameOptionalstring-CSS Classes
disableValueOptionalstringdisabledThe value to use for the disabled state
filterToQueryOptional(text:string)=>object{ q: text }Server filter mapping
formatOptionalfunction-Function to convert the value sent by the API to the value used by the form
helperTextOptionalReactNode-Help text
inputTextOptionalReactNode | (choice) =>stringChoice textRequired if optionText is a custom Component, this function must return the text displayed for the current selection.
optionTextOptionalstring | functionname or record reprField name of record to display in the suggestion item or function which accepts the correct record as argument ((record)=> {string})
optionValueOptionalstringidField name of record containing the value to use as input value
parseOptionalfunction-Function to convert the value from the form to the value sent to the API
placeholderOptionalstring'Search…'Input placeholder
translateChoiceOptionalboolean!isFromReferenceTranslate labels
clearOnBlurOptionalbooleanfalseIf true, clears the filter text in the input when the input loses focus
createOptionalReactElement-A React element rendered when users want to create a new choice
createHintValueOptionalstring-Sentinel value passed to useSupportCreateSuggestion to represent the "create" item internally
createItemLabelOptionalstring | (filter) => ReactNodera.action.create_itemLabel for the "Create …" menu item shown when the filter is non-empty
createLabelOptionalstring | ReactNode-Hint label shown as a menu item when the filter is empty, prompting users that they can create a new option
createValueOptionalstring@@ra-createThe option value stored for the "create" item; must not conflict with real choice values
emptyTextOptionalstring-Accepted for API parity with AutocompleteInput; not rendered in the array variant
handleHomeEndKeysOptionalbooleanfalseIf true, Home/End keys scroll the dropdown list to the first/last item
isOptionEqualToValueOptional(option, value) => booleanareIdsEqualCustom equality check between a choice value and the current field value
limitChoicesToValueOptionalbooleanfalseIf true, the dropdown shows only already-selected choices
matchSuggestionOptional(filter, choice) => boolean-Custom filter function replacing the default substring match; required when optionText is a React element
noOptionsTextOptionalReactNodera.navigation.no_resultsContent shown in the dropdown when no choices match the current filter
onCreateOptional(filter) => object | Promise-Callback invoked with the filter text when the user picks the "create" item; returns or resolves the new choice object
openOnFocusOptionalbooleanfalseAccepted for API parity; the array variant always opens on focus regardless of this setting
optionTextOptionalstring | function | elementname or record reprField name, function, or element used to derive each choice's display label
selectOnFocusOptionalbooleanfalseIf true, all text in the search input is selected when the input receives focus
setFilterOptional(filter: string) => void-Callback fired on every keystroke with the current filter text; for server-side filtering outside ReferenceArrayInput
shouldRenderSuggestionsOptional(filter: string) => boolean() => trueGate function; when it returns false the dropdown stays closed regardless of other open conditions
validateOptionalValidator | Validator[]-Validation

* source and choices are optional inside <ReferenceArrayInput>.

Defining Choices

The list of choices must be an array of objects - one object for each possible choice. In each object, id is the value, and the name is the label displayed to the user.

<AutocompleteArrayInput
  source="roles"
  choices={[
    { id: "admin", name: "Admin" },
    { id: "u001", name: "Editor" },
    { id: "u002", name: "Moderator" },
    { id: "u003", name: "Reviewer" },
  ]}
/>

You can also use an array of objects with different properties for the label and value, given you specify the optionText and optionValue props:

<AutocompleteArrayInput
  source="roles"
  choices={[
    { _id: "admin", label: "Admin" },
    { _id: "u001", label: "Editor" },
    { _id: "u002", label: "Moderator" },
    { _id: "u003", label: "Reviewer" },
  ]}
  optionValue="_id"
  optionText="label"
/>

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

const choices = [
  { id: "admin", name: "myroot.roles.admin" },
  { id: "u001", name: "myroot.roles.u001" },
  { id: "u002", name: "myroot.roles.u002" },
  { id: "u003", name: "myroot.roles.u003" },
];

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 actually editing a one-to-many or a many-to-many relationship. In this case, wrap the <AutocompleteArrayInput> in a <ReferenceArrayInput> component. You don't need to specify the choices prop - the parent component injects it based on the possible values of the related resource.

<ReferenceArrayInput source="tag_ids" reference="tags">
  <AutocompleteArrayInput />
</ReferenceArrayInput>

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

const roles = ["Admin", "Editor", "Moderator", "Reviewer"];
<AutocompleteArrayInput source="roles" choices={roles} />;
// is equivalent to
const choices = roles.map((value) => ({ id: value, name: value }));
<AutocompleteArrayInput source="roles" choices={choices} />;

Using Inside <ReferenceArrayInput>

When used inside a <ReferenceArrayInput>, whenever users type a string in the autocomplete input, <AutocompleteArrayInput> 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}%` });
 
<ReferenceArrayInput source="tag_ids" reference="tags">
  <AutocompleteArrayInput filterToQuery={filterToQuery} />
</ReferenceArrayInput>;

shouldRenderSuggestions

A gate function (filter: string) => boolean called before the dropdown opens. When it returns false, the dropdown stays closed regardless of focus state or other conditions. Use this to suppress suggestions until the user has typed a minimum number of characters.

<AutocompleteArrayInput
  source="tags"
  choices={choices}
  shouldRenderSuggestions={(filter) => filter.length >= 2}
/>

setFilter

A callback (filter: string) => void fired on every keystroke with the current search text. Use this for server-side filtering when the component is not wrapped in a <ReferenceArrayInput> (which handles filtering automatically). No debounce is applied by the component — implement debouncing in the callback if needed.

const [filter, setFilter] = useState("");
 
<AutocompleteArrayInput
  source="tags"
  choices={filteredChoices}
  setFilter={setFilter}
/>;

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.

<AutocompleteArrayInput source="tags" choices={choices} selectOnFocus />

optionText

Determines how each choice's label is derived. Accepts:

  • A string — the name of the field on the choice object to use as the label (default: "name").
  • A function (choice) => string | ReactNode — called with each choice object; return a string or element.
  • A React element — rendered inside a RecordContext containing the choice. When using an element, also pass inputText (for badge labels) and matchSuggestion (for filtering).
<AutocompleteArrayInput source="tags" choices={choices} optionText="label" />

openOnFocus

Accepted for API parity with <AutocompleteInput>. The array variant always opens its dropdown when the inline search input receives focus, so this prop is a no-op — passing it does not change behaviour but allows shared prop objects to be spread across both components without type errors.

<AutocompleteArrayInput source="tags" choices={choices} openOnFocus />

onCreate

A simpler alternative to the create prop. Pass a callback (filter) => newChoice | Promise<newChoice> that receives the current filter text and returns (or resolves with) the new choice object. The returned object is automatically appended to the selection. Use create instead when you need a full custom UI (e.g. a dialog with multiple fields).

<AutocompleteArrayInput
  source="tags"
  choices={choices}
  onCreate={(filter) => ({
    id: String(filter).toLowerCase(),
    name: String(filter),
  })}
/>

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.").

<AutocompleteArrayInput
  source="tags"
  choices={choices}
  noOptionsText="Nothing matches your search"
/>

matchSuggestion

A custom filter function (filter, choice) => boolean that replaces the built-in substring match. Required when optionText is a React element (because the component cannot derive a text representation to search against). The function receives the current filter string and a choice object and must return true when the choice should be shown.

const matchSuggestion = (filter, choice) =>
  choice.first_name.toLowerCase().includes(filter.toLowerCase()) ||
  choice.last_name.toLowerCase().includes(filter.toLowerCase());
 
<AutocompleteArrayInput
  source="author_ids"
  choices={choices}
  matchSuggestion={matchSuggestion}
/>;

limitChoicesToValue

When true, the dropdown only shows choices that are already in the selected values array. All other choices are hidden. Useful in read-heavy UIs where you want users to browse or remove current selections without seeing the full unselected list.

<AutocompleteArrayInput source="tags" choices={choices} limitChoicesToValue />

isOptionEqualToValue

A custom comparator (choiceValue, fieldValue) => boolean that determines when a choice matches a value already in the field array. 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.

<AutocompleteArrayInput
  source="tags"
  choices={choices}
  isOptionEqualToValue={(option, value) => option === value}
/>

handleHomeEndKeys

When true, pressing Home 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.

<AutocompleteArrayInput source="tags" choices={choices} handleHomeEndKeys />

emptyText

Accepted for API parity with <AutocompleteInput> so code that passes emptyText to both variants compiles without errors. The array variant never renders a "(none)" entry — multiple values are deselected by clicking the × badge — so this prop has no visual effect.

<AutocompleteArrayInput source="tags" choices={choices} emptyText="No tags" />

createValue

The sentinel string stored as the option value for the "create new option" item in the dropdown. Defaults to "@@ra-create". Only override this when your actual choices contain that string as an ID.

<AutocompleteArrayInput
  source="tags"
  choices={choices}
  onCreate={(filter) => ({
    id: String(filter).toLowerCase(),
    name: String(filter),
  })}
  createValue="__ra_create__"
/>

createLabel

A hint label rendered as a menu item when the filter text is empty, letting users know they can type to create a new option. Unlike createItemLabel, this appears before the user has typed anything.

<AutocompleteArrayInput
  source="tags"
  choices={choices}
  onCreate={(filter) => ({
    id: String(filter).toLowerCase(),
    name: String(filter),
  })}
  createLabel="Start typing to add a tag"
/>

createItemLabel

The label shown for the "Create …" menu item when the filter text is non-empty. Accepts a string (supports %{item} interpolation for the current filter) or a function (filter) => ReactNode for fully custom rendering. Defaults to the translation of ra.action.create_item ("Create %{item}").

<AutocompleteArrayInput
  source="tags"
  choices={choices}
  onCreate={(filter) => ({
    id: String(filter).toLowerCase(),
    name: String(filter),
  })}
  createItemLabel="Add '%{item}' as a new tag"
/>

createHintValue

An internal sentinel string passed to useSupportCreateSuggestion to identify the "create new option" item in the dropdown. You rarely need to set this — only override it when the default sentinel (@@ra-create) collides with a real choice value in your dataset.

<AutocompleteArrayInput
  source="tags"
  choices={choices}
  onCreate={(filter) => ({ id: filter, name: filter })}
  createHintValue="__new__"
/>

create

Pass a React element to let users create a new option on the fly. <AutocompleteArrayInput> renders a "Create …" menu item at the bottom of the dropdown; clicking it mounts the element you provide. Use useCreateSuggestionContext inside the element to access the current filter text and the onCreate callback.

import { useCreateSuggestionContext } from "ra-core";
 
const CreateTag = () => {
  const { onCancel, onCreate, filter } = useCreateSuggestionContext();
  const [name, setName] = React.useState(filter ?? "");
  return (
    <Dialog open onOpenChange={onCancel}>
      <DialogContent>
        <Input
          value={name}
          onChange={(e) => setName(e.target.value)}
          autoFocus
        />
        <Button onClick={() => onCreate({ id: name.toLowerCase(), name })}>
          Save
        </Button>
      </DialogContent>
    </Dialog>
  );
};
 
<AutocompleteArrayInput
  source="tags"
  choices={choices}
  create={<CreateTag />}
/>;

clearOnBlur

When true, the filter text typed in the search 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.

<AutocompleteArrayInput source="tags" choices={choices} clearOnBlur />

Working With Object Values

When working with a field that contains an array of objects, use parse and format to turn the value into an array of scalar values.

So for instance, for editing the tags field of records looking like the following:

{
  "id": 123,
  "tags": [{ "id": "lifestyle" }, { "id": "photography" }]
}

You should use the following syntax:

<AutocompleteArrayInput
  source="tags"
  parse={(value) => value && value.map((v) => ({ id: v }))}
  format={(value) => value && value.map((v) => v.id)}
  choices={[
    { id: "programming", name: "Programming" },
    { id: "lifestyle", name: "Lifestyle" },
    { id: "photography", name: "Photography" },
  ]}
/>