# Autocomplete

An Autocomplete component allows a user to quickly pick a preset value from a
larger list of possible options.

## Design & usage guidelines

The options in an Autocomplete component should be a list of possible values
that the user can select. These options should be presented in a way that makes
it easy for the user to find the value they are looking for. There are a few
ways to achieve this.

### Sectioned

Section headings can be added to an Autocomplete to break up the options into
groups. This can be useful if there are a lot of options or if the options are
related to different things.

```tsx
import React, { useState } from "react";
import { action } from "storybook/actions";
import {
  Autocomplete,
  type OptionLike,
  defineMenu,
} from "@jobber/components/Autocomplete";

export const AutocompleteV2SectionedExample = () => {
  const [value, setValue] = useState<OptionLike | undefined>();
  const [inputValue, setInputValue] = useState("");
  const menu = defineMenu<OptionLike>([
    {
      type: "section",
      label: "Indoor",
      options: [
        { label: "Drain Cleaning" },
        { label: "Pipe Replacement" },
        { label: "Sewer Line Repair" },
        { label: "Window Cleaning" },
      ],
    },
    {
      type: "section",
      label: "Outdoor",
      options: [
        { label: "Roof Inspection" },
        { label: "Lawn Mowing" },
        { label: "Hedge Trimming" },
      ],
    },
    {
      type: "section",
      label: "Misc",
      options: [
        { label: "Assessment" },
        { label: "Inspection" },
        { label: "2nd Opinion" },
      ],
    },
  ]);

  return (
    <Autocomplete
      version={2}
      placeholder="Search"
      value={value}
      onChange={setValue}
      onBlur={() => action("console.log")("blurred")}
      inputValue={inputValue}
      onInputChange={setInputValue}
      menu={menu}
    />
  );
};
```

### Header and Footers

Headers and Footers can be used to present persistent information or actions
that should always be available regardless of results, or scroll location in the
list of options. While there is no limit to how many can be present, it is
advisable to use them sparringly to prevent the options from being the primary
focus of the Autocomplete.

```tsx
import React, { useState } from "react";
import { action } from "storybook/actions";
import {
  Autocomplete,
  type OptionLike,
  defineMenu,
} from "@jobber/components/Autocomplete";
import { Content } from "@jobber/components/Content";
import { Heading } from "@jobber/components/Heading";
import { Text } from "@jobber/components/Text";
import { Emphasis } from "@jobber/components/Emphasis";

const simpleOptions: OptionLike[] = [
  { label: "Drain Cleaning" },
  { label: "Pipe Replacement" },
  { label: "Sewer Line Repair" },
  { label: "Seasonal Refreshment" },
  { label: "Window Cleaning" },
  { label: "Roof Inspection" },
  { label: "Flooring Installation" },
  { label: "Baseboard Installation" },
  { label: "HVAC Repair" },
  { label: "HVAC Installation" },
];

export function AutocompleteV2HeaderFooterStoryExample() {
  const [value, setValue] = useState<OptionLike | undefined>();
  const [inputValue, setInputValue] = useState("");
  const [lastAction, setLastAction] = useState("");

  return (
    <Content>
      <Heading level={4}>Persistent header/footer actions</Heading>
      <Autocomplete
        version={2}
        placeholder="Search"
        value={value}
        onChange={setValue}
        inputValue={inputValue}
        onBlur={() => action("console.log")("blurred")}
        onFocus={() => action("console.log")("focused")}
        onInputChange={setInputValue}
        menu={defineMenu<OptionLike>([
          {
            type: "header",
            label: "Pinned header",
            shouldClose: false,
            onClick: () => setLastAction("Header clicked"),
          },
          { type: "options", options: simpleOptions },
          {
            type: "footer",
            label: "Pinned footer",
          },
        ])}
      />
      {lastAction && (
        <Text>
          <Emphasis variation="bold">Last action:</Emphasis> {lastAction}
        </Text>
      )}
    </Content>
  );
}
```

### Actions

Actions may be placed either on the "flat" list of options or in individual sections. They are accessible by keyboard using arrow keys and Enter to select the highlighted action.

```tsx
import React, { useState } from "react";
import { action } from "storybook/actions";
import {
  Autocomplete,
  type OptionLike,
  defineMenu,
} from "@jobber/components/Autocomplete";
import { Content } from "@jobber/components/Content";
import { Heading } from "@jobber/components/Heading";
import { Text } from "@jobber/components/Text";
import { Emphasis } from "@jobber/components/Emphasis";

const simpleOptions: OptionLike[] = [
  { label: "Drain Cleaning" },
  { label: "Pipe Replacement" },
  { label: "Sewer Line Repair" },
  { label: "Seasonal Refreshment" },
  { label: "Window Cleaning" },
];

const simpleOptionsSecondSection: OptionLike[] = [
  { label: "Grout Cleaning" },
  { label: "Tile Cleaning" },
  { label: "Lock Repair" },
  { label: "Window Repair" },
  { label: "Door Repair" },
];

const simpleOptionsThirdSection: OptionLike[] = [
  { label: "Yard Work" },
  { label: "Lawn Care" },
  { label: "Tree Removal" },
  { label: "Snow Removal" },
  { label: "Gutter Cleaning" },
];

export function AutocompleteV2WithActionsExample() {
  const [value, setValue] = useState<OptionLike | undefined>();
  const [inputValue, setInputValue] = useState("");
  const [lastAction, setLastAction] = useState("");

  return (
    <Content>
      <Heading level={4}>Section with Actions</Heading>
      <Autocomplete
        version={2}
        placeholder="Search"
        value={value}
        onChange={setValue}
        inputValue={inputValue}
        onBlur={() => action("console.log")("blurred")}
        onInputChange={setInputValue}
        menu={defineMenu<OptionLike>([
          {
            type: "section",
            label: "Services",
            options: simpleOptions,
            actions: [
              {
                type: "action",
                label: "Add Service",
                onClick: () => setLastAction("Add Service clicked"),
              },
            ],
          },
          {
            type: "section",
            label: "Outdoor",
            options: simpleOptionsSecondSection,
            actions: [
              {
                type: "action",
                label: "Add Outdoor Service",
                onClick: () => setLastAction("Add Outdoor Service clicked"),
              },
            ],
          },
          {
            type: "section",
            label: "Extras",
            options: simpleOptionsThirdSection,
            actions: [
              {
                type: "action",
                label: "Add Extras Service",
                onClick: () => setLastAction("Add Extras Service clicked"),
              },
            ],
          },
        ])}
      />
      {lastAction && (
        <Text>
          <Emphasis variation="bold">Last action:</Emphasis> {lastAction}
        </Text>
      )}
    </Content>
  );
}
```

### Empty Actions

Empty actions can be used to present a user with relevant actions to take when
no options exist. They will not be visible otherwise.

```tsx
import React, { useState } from "react";
import {
  Autocomplete,
  type OptionLike,
  defineMenu,
} from "@jobber/components/Autocomplete";
import { Button } from "@jobber/components/Button";
import { Content } from "@jobber/components/Content";
import { Heading } from "@jobber/components/Heading";

export const AutocompleteV2EmptyActionsExample = () => {
  const [value, setValue] = useState<OptionLike | undefined>();
  const [inputValue, setInputValue] = useState("");
  const [open, setOpen] = useState(false);
  const [newService, setNewService] = useState("");

  return (
    <Content>
      <Heading level={5}>Empty actions</Heading>
      <Autocomplete
        version={2}
        placeholder="Try a term with no matches"
        value={value}
        onChange={setValue}
        inputValue={inputValue}
        onInputChange={setInputValue}
        emptyStateMessage="No services found"
        emptyActions={[
          {
            type: "action",
            label: "Create service",
            onClick: () => setOpen(true),
          },
        ]}
        menu={defineMenu<OptionLike>([
          {
            type: "options",
            options: [
              { label: "Drain Cleaning" },
              { label: "Pipe Replacement" },
              { label: "Sewer Line Repair" },
            ],
          },
        ])}
      />
      {open ? (
        <Content>
          <Heading level={5}>Create service</Heading>
          <input
            value={newService}
            onChange={e => setNewService(e.target.value)}
          />
          <Button label="Create" onClick={() => setOpen(false)} />
        </Content>
      ) : null}
    </Content>
  );
};
```

### Stay Open Behavior

All interactive elements (actions, headers, footers, empty actions) can be individually configured to keep the menu open when used if
desired.

```tsx
import React, { useState } from "react";
import {
  Autocomplete,
  type OptionLike,
  defineMenu,
} from "@jobber/components/Autocomplete";
import { Content } from "@jobber/components/Content";
import { Heading } from "@jobber/components/Heading";

export const AutocompleteV2StayOpenExample = () => {
  const [value, setValue] = useState<OptionLike | undefined>();
  const [inputValue, setInputValue] = useState("");

  return (
    <Content>
      <Heading level={5}>Stay Open Action</Heading>
      <Autocomplete
        version={2}
        placeholder="Search"
        value={value}
        onChange={setValue}
        inputValue={inputValue}
        onInputChange={setInputValue}
        menu={defineMenu<OptionLike>([
          {
            type: "options",
            options: [
              { label: "Drain Cleaning" },
              { label: "Pipe Replacement" },
              { label: "Sewer Line Repair" },
            ],
            actions: [
              {
                type: "action",
                label: "Add Service (stays open)",
                shouldClose: false,
                onClick: () => alert("Add Service"),
              },
            ],
          },
        ])}
      />
    </Content>
  );
};
```

### FreeForm

The "free form" allows for using a text value that does not already exist in the
list of options. This works best when the content of an option is simply a word.
When the content is more complex, we have to consider what to populate the
additional fields with.

An alternate approach with complex data, is to leverage the empty actions,
actions, or header/footer actions to launch a creation flow to populate the
fields.

```tsx
import React, { useState } from "react";
import {
  Autocomplete,
  type OptionLike,
  defineMenu,
} from "@jobber/components/Autocomplete";
import { Content } from "@jobber/components/Content";
import { Heading } from "@jobber/components/Heading";
import { Text } from "@jobber/components/Text";

const simpleOptions: OptionLike[] = [
  { label: "Drain Cleaning" },
  { label: "Pipe Replacement" },
  { label: "Sewer Line Repair" },
  { label: "Seasonal Refreshment" },
  { label: "Window Cleaning" },
  { label: "Roof Inspection" },
  { label: "Flooring Installation" },
  { label: "Baseboard Installation" },
  { label: "HVAC Repair" },
  { label: "HVAC Installation" },
];

export function AutocompleteV2FreeFormStoryExample() {
  const [value, setValue] = useState<OptionLike | undefined>();
  const [inputValue, setInputValue] = useState("");

  return (
    <Content>
      <Heading level={4}>Free-form create</Heading>
      <Autocomplete
        version={2}
        placeholder="Type anything"
        value={value}
        onChange={setValue}
        inputValue={inputValue}
        onInputChange={setInputValue}
        allowFreeForm
        createFreeFormValue={label => ({ label })}
        menu={defineMenu<OptionLike>([
          { type: "options", options: simpleOptions },
        ])}
      />
      <Text>Try typing an option not in the list, and blurring the input</Text>
      <Heading level={5}>Selected value: {value?.label}</Heading>
    </Content>
  );
}
```

### Interactive Content

Content in options, actions, headers, footers, and sections can all be
customized, however it is imperative to avoid putting additional interactive
elements inside any of these. Because focus remains in the input, and we
"virtually" move the highlighted item with arrow keys on keyboard navigation, it
is impossible to Tab to any nested interactive elements such as a button inside
of a row. Instead, use the provided action interfaces.

### Content Complexity

When providing complex content in each option, it is recommended to separate the
options with a border bottom to improve readability of the options.

### Content Consistency

Items of the same type should appear consistently ie. if an Autocomplete has
options, sections and actions avoid making the appearance of some options
different from the rest.

## Related components

* If you want to present a list of predefined options without text input, or the
  number of options is smaller, use a [Select](../Select/Select.md)
* If autocompleted results are not required for the text input, use
  [InputText](../InputText/InputText.md)
* If a text input appearance is not required, and options can only be from a
  predefined set then a Combobox can be used


## Configuration

### Legacy to Supported

Legacy has a single mechanism to update the options. Only `initialOptions` are
directly controlled, then `getOptions` is used to populate the options based on
the input term. This is managed with internal state that creates barriers as to
what can be done with the experience.

Now, Autocomplete is entirely controlled, having both sets of `inputValue`,
`onInputChange`, `value` and `onChange`.

Filtering is now built into Autocomplete. It has a default filter of comparing
the input value to the case insensitive "label" value. If using custom data,
where we might want to search a different field, or even multiple fields,
providing filterOptions allows customization of the filter logic. For cases with
async data it is recommended to opt out of filtering entirely, and manage that
yourself with the results you return and pass as options. This can be done with
`filterOptions={false}`.

Autocomplete has been decoupled from React Hook Form, and has no built in
validation. To validate, you must create your own logic and use either `error`
or `invalid` to set the visually invalid state on the input. `onBlur`, `value`,
`onChange`, `inputValue`, `onInputChange` provide all the necessary callbacks to
determine the state of the Autocomplete. These callbacks will integrate with a
schema like Zod, and an external instance of RHF.

Filtering is now built in, and not necessary to implement yourself. You are able
to opt out or customize it if desired, see more about this in the filtering
section.

While sections still exist, the additional pre defined "details" and
"description" variations are not directly available by default. They can be
re-created with custom data, custom layouts, and modified option/section styles.

All options will by default continue to be visible after making a selection
whereas Legacy would limit your options to the selected option if a selection is
present.

### Structure, `menu` and `type`s

The `menu` prop accepts 2 different possible "top level" types: "options" and
"section". For a "flat" set of options, simply provide "options" where the
minimum required data is `label: string`. For a sectioned set of options,
provide "section" which also has a minimum `label: string` in addition to an
`options` key.

Each section will be rendered in the order provided, the same is true of the
options.

While it is possible to combine both flat options and sectioned options, it is
generally not recommended. The inconsistency of having sections for only some
options can lead to a confusing user experience.

Options can be selected with the enter key if it active/highlighted, or with a
mouse click. Space will do nothing because focus is intentionally kept in the
input for further refinements to the search term.

Legacy Section vs Supported

```
 const SectionHeadingOptions = [
   {
     label: "Ships",
     options: [
       { value: 1, label: "Sulaco" },
       { value: 2, label: "Nostromo" },
       { value: 3, label: "Serenity" },
       { value: 4, label: "Sleeper Service" },
       { value: 5, label: "Enterprise" },
       { value: 6, label: "Enterprise-D" },
     ],
   },
   {
     label: "Planets",
     options: [
       { value: 7, label: "Endor" },
       { value: 8, label: "Vulcan" },
       { value: 9, label: "Bespin" },
       { value: 10, label: "Tatooine" },
     ],
   },
 ];

 const [value, setValue] = useState<Option | undefined>();

 return (
   <Autocomplete
     initialOptions={SectionHeadingOptions}
     placeholder="Search for something under a section heading"
     value={value}
     onChange={newValue => setValue(newValue)}
     getOptions={getOptions}
   />
 );

 function getOptions(text: string) {
   if (text === "") {
     return headingOptions;
   }
   const filterRegex = new RegExp(text, "i");

   return headingOptions.map(section => ({
     ...section,
     options: section.options.filter(option =>
       option.label.match(filterRegex),
     ),
   }));
 }

 // VERSION 2 BELOW

 const sectionMenu = [
   {
     type: "section",
     label: "Ships",
     options: [
       { label: "Sulaco" },
       { label: "Nostromo" },
       { label: "Serenity" },
       { label: "Sleeper Service" },
       { label: "Enterprise" },
       { label: "Enterprise-D" },
     ]
   },
   {
     type: "section",
     label: "Planets",
     options: [
       { label: "Endor" },
       { label: "Vulcan" },
       { label: "Bespin" },
       { label: "Tatooine" },
     ],
   },
 ];

 const [value, setValue] = useState(undefined);
 const [inputValue, setInputValue] = useState("");

 return(
   <Autocomplete
     version={2}
     menu={sectionMenu}
     value={value}
     onChange={setValue}
     inputValue={inputValue}
     onInputChange={setInputValue}
     placeholder="Search for something under a section heading"
   />
 );
```

### Additional Elements

#### Actions

On top of the basics, there are also "actions" that can be used either on the
"options" or "section" type. These will always be rendered at the bottom of
their respective grouping whether that is a section or flat top level options.

Actions have the same interaction mechanisms. They require a `type: "action"`,
`label` and an `onClick`.

Actions have an additional optional key of `shouldClose` that is `true` by
default. This causes the open menu to close when an option is interacted with.
It can be set to true on each individual option to customize each action's
desired behavior.

#### Header/Footer

These elements can be either text-only, or interactive like actions. They
implement the same API as actions with `shouldClose`, `onClick`, and `label`. In
the absence of an `onClick` it will be a non interactive Header/Footer,
providing `onClick` is the signal for it to be interactive.

These elements do not respond to scrolling, they are always at the top or bottom
and will continue to be displayed even if no options exist after filtering and
the empty state is visible.

### Highlighting/Active Index

All interactive items can be navigated with arrow keys. Sections, and other
elements like non interactive Headers and Footers will be skipped by arrow key
navigation.

We reset the active index during the majority of interactions with respect to
typing.

### Custom Data & Rendering

While `options`, `section`s, `action`s, `footer`s, and `header`s all have a
minimum of `label` they can all be enhanced with additional key/values of your
choosing. These values will be accessible in their respective `customRender`
methods.

Each `customRender` will have slightly different arguments, with interactive
elements receiving `isActive` and `option`s receiving `isSelected`. See the prop
types for more details. Any custom data will be passed to these render functions
allowing you to use the data as needed to create a customized layout.

In addition to the aforementioned elements, there are also `customRender`
methods for the input itself, and the loading state.

UNSAFE styles and classnames exist to override the styles of all the main
elements.

With simple data, "label" is used for most operations and logic, if with custom
data the "label" is no longer the data to use there are methods such as
`getOptionLabel`, `inputEqualsOption` and `isOptionEqualToValue` to customize
the logic to use something other than "label".

### Empty State

There is a default empty state of "No options" that can be modified with the
`emptyStateMessage` prop, which can also accept more complex markup than a
string if desired.

Additionally, if one or more actions that only appear when no options are found
is desired, the `emptyActions` prop can be used. The actions implement the same
API as other Actions and interactive Header/Footers.

### Async

Taking full control of the options and fetching new/different options from an
API query is possible.

Since the `menu` controls what options are displayed, when implementing an async
instance it is advisable to opt out of the internal filtering, and reduce the
`debounce` value to 0 so that filtering is effectively managed outside the
component and only the options relevant to the search term are passed to `menu`.

### Navigation

For keyboard users, navigation is done entirely through arrow keys. Tabbing will
move focus onto the next focusable element that is not the Autocomplete.

When providing custom content, it is imperative to avoid providing any elements
that would require keyboard focus to activate. For example providing 2 buttons
within a single row, or even a single button would be impossible to access with
keyboard. If you require an interactive element, please use an Action,
interactive Header, or interactive Footer.

### Allow FreeForm

This is `false` by default, and allows a user to provide a value that is not an
option in the list.

> **WARNING:** Using free form with custom data introduces complexity due to the fact that
> the input's value can only be a string, so if your custom data has additional
> fields like `details` for example, we have no way to populate that field.

To help wih this, we require a `createFreeFormValue` method when using
`allowFreeForm`. This method is applied to the outgoing value before it is
passed to `onChange` to change the shape of the returned value into the same
shape as the incoming custom data, where you must provide the value for these
additional fields.

`allowFreeForm` with simple items having only "label" is the easiest way to
leverage this prop. More complex creation flows may be better suited for complex
data.

### onChange

Autocomplete has some nuance as far as `onChange` and when we consider the
Autocomplete's selection to have changed.

Since we can't know if the input value is only being used to refine results to
an option to select, or if it will be used as a free form value itself - we must
wait until blur to definitively say a non-explicit selection has been made.

There are of course some explicit commit signals such as selecting an option
with a click, or Enter press with an item highlighted. Another is if there is an
existing selection, clearing it is considered an `onChange` because the user
actively chose to remove the selection.

If free form is not allowed, blurring will clear the input unless the content
exactly matches an option.

### Keys

By default, each element's `label` is used as its React key. If any two items
have the same label, or label can otherwise not be guaranteed to be unique -
then you must provide a `key` on the object. This key on the objects is
reserved, and used exclusively for this purpose.

### Multiple

This is not yet implemented fully. Avoid using.


## Props

### Web

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `createFreeFormValue` | `(input: string) => Value` | Yes | — | Factory used to create a Value from free-form input when committing. Necessary with complex option values. The only v... |
| `inputValue` | `string` | Yes | — | The current input value of the Autocomplete. |
| `menu` | `MenuItem<Value, ExtraProps, ExtraProps>[]` | Yes | — | Data structure for the menu. Observes a data hierarchy to determine elements, order, and grouping. Accepts Sections, ... |
| `onChange` | `((value: AutocompleteValue<Value, Multiple>) => void) | ((value: AutocompleteValue<Value, Multiple>) => void)` | Yes | — | Callback invoked when the selection value changes. This is called when we consider a selection "committed" - The user... |
| `onInputChange` | `(value: string) => void` | Yes | — | Callback invoked when the input value changes. |
| `value` | `Value[] | OptionLike` | Yes | — | The currently selected value of the Autocomplete. Single-select: undefined indicates no selection |
| `version` | `2` | Yes | — | Version 2 is highly experimental. Avoid using it unless you have talked with Atlantis first. |
| `allowFreeForm` | `boolean` | No | — | Whether the autocomplete allows free-form input. When true, the input value is not restricted to the options * in the... |
| `aria-describedby` | `string` | No | — | Identifies the element (or elements) that describes the object. @see {@link https://www.w3.org/TR/wai-aria-1.2/#aria-... |
| `aria-details` | `string` | No | — | Identifies the element (or elements) that provide a detailed, extended description. @see {@link https://www.w3.org/TR... |
| `aria-label` | `string` | No | — | Defines a string value that labels the current element. @see {@link https://www.w3.org/TR/wai-aria-1.2/#aria-label} |
| `aria-labelledby` | `string` | No | — | Identifies the element (or elements) that labels the current element. @see {@link https://www.w3.org/TR/wai-aria-1.2/... |
| `aria-required` | `Booleanish` | No | — | Indicates that user input is required before form submission. @see {@link https://www.w3.org/TR/wai-aria-1.2/#aria-re... |
| `autoFocus` | `boolean` | No | — | Whether the input should be auto-focused (React casing). |
| `clearable` | `Clearable` | No | — | Add a clear action on the input that clears the value. |
| `customRenderAction` | `(args: { value: MenuAction<ExtraProps>; isActive: boolean; origin?: ActionOrigin; }) => ReactNode` | No | — | Render prop to customize the rendering of an action. @param args.value - The action value including all extra keys fr... |
| `customRenderFooter` | `(args: { value: MenuFooter<ExtraProps>; isActive?: boolean; }) => ReactNode` | No | — | Render prop to customize the rendering of footer items. |
| `customRenderHeader` | `(args: { value: MenuHeader<ExtraProps>; isActive?: boolean; }) => ReactNode` | No | — | Render prop to customize the rendering of header items. |
| `customRenderInput` | `(props: { inputRef: Ref<HTMLInputElement | HTMLTextAreaElement>; inputProps: InputTextRebuiltProps; }) => ReactNode` | No | — | Render prop to customize the rendering of the input. @param props.inputRef - The ref to the input element @param prop... |
| `customRenderLoading` | `ReactNode` | No | — | Custom render prop for content to render when `loading` is true. |
| `customRenderOption` | `(args: { value: Value; isActive: boolean; isSelected: boolean; }) => ReactNode` | No | — | Render prop to customize the rendering of an option. @param args.value - The option value including all extra keys fr... |
| `customRenderSection` | `(section: MenuSection<Value, ExtraProps, ExtraProps>) => ReactNode` | No | — | Render prop to customize the rendering of a section. @param args.section - The section value including all extra keys... |
| `customRenderValue` | `(args: { value: Value; getOptionLabel: (option: Value) => string; }) => ReactNode` | No | — | Render prop to customize the content inside each selection chip. Only applicable in `multiple` mode. The Autocomplete... |
| `debounce` | `number` | No | `300` | Debounce in milliseconds for input-driven filtering and search render. Set to 0 to disable debouncing. |
| `description` | `ReactNode` | No | — | Further description of the input, can be used for a hint. |
| `disabled` | `boolean` | No | — | Whether the input is disabled. |
| `emptyActions` | `MenuAction<ExtraProps>[] | ((args: { inputValue: string; }) => MenuAction<ExtraProps>[])` | No | — | Actions to display when there are no options to render after filtering. Can be a static list or a function that deriv... |
| `emptyStateMessage` | `ReactNode` | No | `string "No options"` | Render a custom empty state when the menu is empty. NOTE: Do not put interactive elements in the empty state, it will... |
| `error` | `string` | No | — | Error message to display. This also highlights the field red. |
| `filterOptions` | `false | ((options: Value[], inputValue: string) => Value[])` | No | — | Controls how options are filtered in response to the current input value. - Omit to use the default case-insensitive ... |
| `getOptionLabel` | `(option: Value) => string` | No | — | Used to determine the label for a given option, useful for custom data for options. Defaults to  option.label. |
| `inputEqualsOption` | `(input: string, option: Value) => boolean` | No | — | Custom equality for input text to option mapping. Defaults to case-sensitive label equality via getOptionLabel. |
| `invalid` | `boolean` | No | — | Highlights the field red to indicate an error. |
| `isOptionEqualToValue` | `(option: Value, value: Value) => boolean` | No | — | Custom equality for option to value mapping. |
| `limitSelectionText` | `(truncatedCount: number) => ReactNode` | No | `(count) => `+${count}`` | Function to generate the label displayed when selections are truncated by `limitVisibleSelections`. Receives the numb... |
| `limitVisibleSelections` | `number` | No | `6` | Maximum number of selection chips visible when the input is not focused. When the input gains focus, all selections a... |
| `loading` | `boolean` | No | — | Show a spinner to indicate loading. |
| `multiple` | `boolean` | No | — | Whether the autocomplete allows multiple selections. When true, selected values are displayed as dismissible chips ab... |
| `name` | `string` | No | — | The name attribute for the input element. |
| `onBlur` | `(event: FocusEvent<HTMLInputElement | HTMLTextAreaElement, Element>) => void` | No | — | Blur event handler. |
| `onClose` | `() => void` | No | — | Callback invoked when the menu closes. |
| `onFocus` | `(event: FocusEvent<HTMLInputElement | HTMLTextAreaElement, Element>) => void` | No | — | Focus event handler. |
| `onOpen` | `() => void` | No | — | Callback invoked when the menu opens. |
| `openOnFocus` | `boolean` | No | `true` | Whether the menu should open when the input gains focus. Note: Clicking on the input will always open the menu. openO... |
| `placeholder` | `string` | No | — | Text that appears inside the input when empty and floats above the value as a mini label once the user enters a value... |
| `prefix` | `Affix` | No | — | Adds a prefix label and icon to the field. |
| `readOnly` | `boolean` | No | — | Whether the input is read-only (HTML standard casing). |
| `ref` | `Ref<HTMLInputElement | HTMLTextAreaElement>` | No | — |  |
| `size` | `"small" | "large"` | No | — | Adjusts the interface to either have small or large spacing. |
| `suffix` | `{ onClick: () => void; readonly ariaLabel: string; readonly icon: IconNames; readonly label?: string; } | { onClick?: never; ariaLabel?: never; readonly label?: string; readonly icon?: IconNames; }` | No | — | Adds a suffix label and icon with an optional action to the field. |
| `UNSAFE_className` | `{ menu?: string; option?: string; section?: string; action?: string; input?: string; header?: string; footer?: string; selection?: string; }` | No | — | **Use at your own risk:** Custom class names for specific elements. This should only be used as a **last resort**. Us... |
| `UNSAFE_styles` | `{ menu?: CSSProperties; option?: CSSProperties; section?: CSSProperties; action?: CSSProperties; input?: CSSProperties; header?: CSSProperties; footer?: CSSProperties; selection?: CSSProperties; }` | No | — | **Use at your own risk:** Custom style for specific elements. This should only be used as a **last resort**. Using th... |
