Autocomplete
An Autocomplete is a component that allows for real-time suggestions from a
pre-determined list as the user types by filtering data based on the current
value. It can also be used to interact with an API that handles the sorting,
filtering, matching, etc as well.
Simple Example
An Autocomplete requires a list of options which can be strings or an object
with a label or name string so that the options can be filtered as the user
types. In addition, a listboxLabel or listboxLabelledby prop must be
defined for accessibility to provide a label for the listbox.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { type ReactElement } from "react";
export default function SimpleExample(): ReactElement {
return (
<Autocomplete
label="Fruit"
placeholder="Apple"
listboxLabel="Fruits"
options={fruits}
/>
);
}
export const fruits = [
"Apple",
"Apricot",
"Banana",
"Blueberry",
"Cranberry",
"Kiwi",
"Mango",
"Orange",
"Peach",
"Plum",
"Strawberry",
];
Simple Label Example
This is the same example as above, but using options as { label: string }.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { type ReactElement } from "react";
export default function SimpleLabelExample(): ReactElement {
return (
<Autocomplete
label="Fruit"
placeholder="Apple"
listboxLabel="Fruits"
options={options}
/>
);
}
const options = [
{ label: "Apple" },
{ label: "Apricot" },
{ label: "Banana" },
{ label: "Blueberry" },
{ label: "Cranberry" },
{ label: "Kiwi" },
{ label: "Mango" },
{ label: "Orange" },
{ label: "Peach" },
{ label: "Plum" },
{ label: "Strawberry" },
];
Object Options
The Autocomplete can support any object option by also providing an
getOptionLabel prop that will return a string value for each option.
See Typescript Typing for type behavior.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { type ReactElement } from "react";
export default function ObjectOptionsExample(): ReactElement {
return (
<Autocomplete label="State" options={desserts} listboxLabel="States" />
);
}
export interface Dessert {
name: string;
calories: number;
fat: number;
carbs: number;
protein: number;
sodium: number;
calcium: number;
iron: number;
type: "Ice cream" | "Pastry" | "Other";
}
export type DessertKey = keyof Dessert;
export const desserts: readonly Dessert[] = [
{
name: "Frozen yogurt",
type: "Ice cream",
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
sodium: 87,
calcium: 14,
iron: 1,
},
{
name: "Ice cream sandwich",
type: "Ice cream",
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
sodium: 129,
calcium: 8,
iron: 1,
},
{
name: "Eclair",
type: "Pastry",
calories: 262,
fat: 16.0,
carbs: 37,
protein: 6.0,
sodium: 337,
calcium: 6,
iron: 7,
},
{
name: "Cupcake",
type: "Pastry",
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
sodium: 413,
calcium: 3,
iron: 8,
},
{
name: "Gingerbread",
type: "Pastry",
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
sodium: 327,
calcium: 7,
iron: 16,
},
{
name: "Jelly bean",
type: "Other",
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
sodium: 50,
calcium: 0,
iron: 0,
},
{
name: "Lollipop",
type: "Other",
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0.0,
sodium: 38,
calcium: 0,
iron: 2,
},
{
name: "Honeycomb",
type: "Other",
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
sodium: 562,
calcium: 0,
iron: 45,
},
{
name: "Donut",
type: "Pastry",
calories: 52,
fat: 25.0,
carbs: 51,
protein: 4.9,
sodium: 326,
calcium: 2,
iron: 22,
},
{
name: "KitKat",
type: "Other",
calories: 16,
fat: 6.0,
carbs: 65,
protein: 7.0,
sodium: 54,
calcium: 12,
iron: 6,
},
];
export const dessertColumns = Object.keys(desserts[0]) as readonly DessertKey[];
Option Props
The Autocomplete will automatically pass through common Option props that
exist on each option.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { cssUtils } from "@react-md/core/cssUtils";
import FavoriteIcon from "@react-md/material-icons/FavoriteIcon";
import { type ReactElement } from "react";
const options = [
{
label: "Favorite Left Addon",
leftAddon: <FavoriteIcon />,
},
{
label: "Favorite Right Addon",
// children will be shown in the Option by default and should normally
// contain the same text as the `label` since the label is the searchable
// part. the main usage of the `children` is to apply any custom styles or
// highlighting
children: (
<>
<span className={cssUtils({ fontWeight: "bold" })}>Favorite</span>{" "}
<span className={cssUtils({ textDecoration: "underline" })}>Right</span>{" "}
Addon
</>
),
rightAddon: <FavoriteIcon />,
},
{
label: "Multiline",
secondaryText: "Second line of text that is ignored in filtering",
multiline: true,
},
];
export default function OptionPropsExample(): ReactElement {
return (
<Autocomplete label="Label" options={options} listboxLabel="Options" />
);
}
Get Option Props
Another way to pass props to each option is using the getOptionProps function.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { Avatar } from "@react-md/core/avatar/Avatar";
import { cssUtils } from "@react-md/core/cssUtils";
import { cnb } from "cnbuilder";
import { type ReactElement } from "react";
export default function GetOptionPropsExample(): ReactElement {
return (
<Autocomplete
label="Dessert"
options={desserts}
getOptionProps={({ index, option }) => ({
className: cnb(
index % 3 === 0 && cssUtils({ textDecoration: "line-through" }),
),
leftAddon: <Avatar size="icon">{option.type.charAt(0)}</Avatar>,
})}
listboxLabel="Desserts"
/>
);
}
export interface Dessert {
name: string;
calories: number;
fat: number;
carbs: number;
protein: number;
sodium: number;
calcium: number;
iron: number;
type: "Ice cream" | "Pastry" | "Other";
}
export type DessertKey = keyof Dessert;
export const desserts: readonly Dessert[] = [
{
name: "Frozen yogurt",
type: "Ice cream",
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
sodium: 87,
calcium: 14,
iron: 1,
},
{
name: "Ice cream sandwich",
type: "Ice cream",
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
sodium: 129,
calcium: 8,
iron: 1,
},
{
name: "Eclair",
type: "Pastry",
calories: 262,
fat: 16.0,
carbs: 37,
protein: 6.0,
sodium: 337,
calcium: 6,
iron: 7,
},
{
name: "Cupcake",
type: "Pastry",
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
sodium: 413,
calcium: 3,
iron: 8,
},
{
name: "Gingerbread",
type: "Pastry",
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
sodium: 327,
calcium: 7,
iron: 16,
},
{
name: "Jelly bean",
type: "Other",
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
sodium: 50,
calcium: 0,
iron: 0,
},
{
name: "Lollipop",
type: "Other",
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0.0,
sodium: 38,
calcium: 0,
iron: 2,
},
{
name: "Honeycomb",
type: "Other",
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
sodium: 562,
calcium: 0,
iron: 45,
},
{
name: "Donut",
type: "Pastry",
calories: 52,
fat: 25.0,
carbs: 51,
protein: 4.9,
sodium: 326,
calcium: 2,
iron: 22,
},
{
name: "KitKat",
type: "Other",
calories: 16,
fat: 6.0,
carbs: 65,
protein: 7.0,
sodium: 54,
calcium: 12,
iron: 6,
},
];
export const dessertColumns = Object.keys(desserts[0]) as readonly DessertKey[];
Disabled Options
An option can be disabled by adding disabled: true to the option or using getOptionProps
to return disabled: true.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { type ReactElement } from "react";
export default function DisabledOptionsExample(): ReactElement {
return (
<Autocomplete
label="Dessert"
options={desserts.map((dessert, i) => ({
...dessert,
disabled: i % 4 === 0,
}))}
// or try
// getOptionProps={({ index, option }) => ({
// disabled: option.disabled || index % 4 === 0 || option.type === "Other",
// })}
listboxLabel="Desserts"
/>
);
}
export interface Dessert {
name: string;
calories: number;
fat: number;
carbs: number;
protein: number;
sodium: number;
calcium: number;
iron: number;
type: "Ice cream" | "Pastry" | "Other";
}
export type DessertKey = keyof Dessert;
export const desserts: readonly Dessert[] = [
{
name: "Frozen yogurt",
type: "Ice cream",
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
sodium: 87,
calcium: 14,
iron: 1,
},
{
name: "Ice cream sandwich",
type: "Ice cream",
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
sodium: 129,
calcium: 8,
iron: 1,
},
{
name: "Eclair",
type: "Pastry",
calories: 262,
fat: 16.0,
carbs: 37,
protein: 6.0,
sodium: 337,
calcium: 6,
iron: 7,
},
{
name: "Cupcake",
type: "Pastry",
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
sodium: 413,
calcium: 3,
iron: 8,
},
{
name: "Gingerbread",
type: "Pastry",
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
sodium: 327,
calcium: 7,
iron: 16,
},
{
name: "Jelly bean",
type: "Other",
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
sodium: 50,
calcium: 0,
iron: 0,
},
{
name: "Lollipop",
type: "Other",
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0.0,
sodium: 38,
calcium: 0,
iron: 2,
},
{
name: "Honeycomb",
type: "Other",
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
sodium: 562,
calcium: 0,
iron: 45,
},
{
name: "Donut",
type: "Pastry",
calories: 52,
fat: 25.0,
carbs: 51,
protein: 4.9,
sodium: 326,
calcium: 2,
iron: 22,
},
{
name: "KitKat",
type: "Other",
calories: 16,
fat: 6.0,
carbs: 65,
protein: 7.0,
sodium: 54,
calcium: 12,
iron: 6,
},
];
export const dessertColumns = Object.keys(desserts[0]) as readonly DessertKey[];
Controlling the Value
If the current value is needed, provide a value and setValue prop.
Value: "null"
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { Box } from "@react-md/core/box/Box";
import { Typography } from "@react-md/core/typography/Typography";
import { type ReactElement, useState } from "react";
export default function ControllingTheValueExample(): ReactElement {
const [value, setValue] = useState<State | null>(null);
return (
<Box align="start" stacked>
<Autocomplete
label="State"
value={value}
setValue={setValue}
options={states}
listboxLabel="States"
/>
<Typography>Value: {`"${value?.name || null}"`}</Typography>
</Box>
);
}
export const states = [
{
name: "Alabama",
abbreviation: "AL",
},
{
name: "Alaska",
abbreviation: "AK",
},
{
name: "American Samoa",
abbreviation: "AS",
},
{
name: "Arizona",
abbreviation: "AZ",
},
{
name: "Arkansas",
abbreviation: "AR",
},
{
name: "California",
abbreviation: "CA",
},
{
name: "Colorado",
abbreviation: "CO",
},
{
name: "Connecticut",
abbreviation: "CT",
},
{
name: "Delaware",
abbreviation: "DE",
},
{
name: "District Of Columbia",
abbreviation: "DC",
},
{
name: "Federated States Of Micronesia",
abbreviation: "FM",
},
{
name: "Florida",
abbreviation: "FL",
},
{
name: "Georgia",
abbreviation: "GA",
},
{
name: "Guam",
abbreviation: "GU",
},
{
name: "Hawaii",
abbreviation: "HI",
},
{
name: "Idaho",
abbreviation: "ID",
},
{
name: "Illinois",
abbreviation: "IL",
},
{
name: "Indiana",
abbreviation: "IN",
},
{
name: "Iowa",
abbreviation: "IA",
},
{
name: "Kansas",
abbreviation: "KS",
},
{
name: "Kentucky",
abbreviation: "KY",
},
{
name: "Louisiana",
abbreviation: "LA",
},
{
name: "Maine",
abbreviation: "ME",
},
{
name: "Marshall Islands",
abbreviation: "MH",
},
{
name: "Maryland",
abbreviation: "MD",
},
{
name: "Massachusetts",
abbreviation: "MA",
},
{
name: "Michigan",
abbreviation: "MI",
},
{
name: "Minnesota",
abbreviation: "MN",
},
{
name: "Mississippi",
abbreviation: "MS",
},
{
name: "Missouri",
abbreviation: "MO",
},
{
name: "Montana",
abbreviation: "MT",
},
{
name: "Nebraska",
abbreviation: "NE",
},
{
name: "Nevada",
abbreviation: "NV",
},
{
name: "New Hampshire",
abbreviation: "NH",
},
{
name: "New Jersey",
abbreviation: "NJ",
},
{
name: "New Mexico",
abbreviation: "NM",
},
{
name: "New York",
abbreviation: "NY",
},
{
name: "North Carolina",
abbreviation: "NC",
},
{
name: "North Dakota",
abbreviation: "ND",
},
{
name: "Northern Mariana Islands",
abbreviation: "MP",
},
{
name: "Ohio",
abbreviation: "OH",
},
{
name: "Oklahoma",
abbreviation: "OK",
},
{
name: "Oregon",
abbreviation: "OR",
},
{
name: "Pennsylvania",
abbreviation: "PA",
},
{
name: "Puerto Rico",
abbreviation: "PR",
},
{
name: "Rhode Island",
abbreviation: "RI",
},
{
name: "South Carolina",
abbreviation: "SC",
},
{
name: "South Dakota",
abbreviation: "SD",
},
{
name: "Tennessee",
abbreviation: "TN",
},
{
name: "Texas",
abbreviation: "TX",
},
{
name: "Utah",
abbreviation: "UT",
},
{
name: "Vermont",
abbreviation: "VT",
},
{
name: "Virgin Islands",
abbreviation: "VI",
},
{
name: "Virginia",
abbreviation: "VA",
},
{
name: "Washington",
abbreviation: "WA",
},
{
name: "West Virginia",
abbreviation: "WV",
},
{
name: "Wisconsin",
abbreviation: "WI",
},
{
name: "Wyoming",
abbreviation: "WY",
},
] as const;
export type StateName = (typeof states)[number]["name"];
export type StateAbbreviation = (typeof states)[number]["abbreviation"];
export interface State {
name: StateName;
abbreviation: StateAbbreviation;
}
Setting a default value
If the value does not need to be controlled but one of the options should
be selected by default, set the defaultValue prop instead.
See Typescript Typing for type behavior.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { type ReactElement } from "react";
export default function SettingADefaultValueExample(): ReactElement {
const defaultValue: State = states[9];
return (
<Autocomplete
label="State"
options={states}
defaultValue={defaultValue}
listboxLabel="States"
/>
);
}
export const states = [
{
name: "Alabama",
abbreviation: "AL",
},
{
name: "Alaska",
abbreviation: "AK",
},
{
name: "American Samoa",
abbreviation: "AS",
},
{
name: "Arizona",
abbreviation: "AZ",
},
{
name: "Arkansas",
abbreviation: "AR",
},
{
name: "California",
abbreviation: "CA",
},
{
name: "Colorado",
abbreviation: "CO",
},
{
name: "Connecticut",
abbreviation: "CT",
},
{
name: "Delaware",
abbreviation: "DE",
},
{
name: "District Of Columbia",
abbreviation: "DC",
},
{
name: "Federated States Of Micronesia",
abbreviation: "FM",
},
{
name: "Florida",
abbreviation: "FL",
},
{
name: "Georgia",
abbreviation: "GA",
},
{
name: "Guam",
abbreviation: "GU",
},
{
name: "Hawaii",
abbreviation: "HI",
},
{
name: "Idaho",
abbreviation: "ID",
},
{
name: "Illinois",
abbreviation: "IL",
},
{
name: "Indiana",
abbreviation: "IN",
},
{
name: "Iowa",
abbreviation: "IA",
},
{
name: "Kansas",
abbreviation: "KS",
},
{
name: "Kentucky",
abbreviation: "KY",
},
{
name: "Louisiana",
abbreviation: "LA",
},
{
name: "Maine",
abbreviation: "ME",
},
{
name: "Marshall Islands",
abbreviation: "MH",
},
{
name: "Maryland",
abbreviation: "MD",
},
{
name: "Massachusetts",
abbreviation: "MA",
},
{
name: "Michigan",
abbreviation: "MI",
},
{
name: "Minnesota",
abbreviation: "MN",
},
{
name: "Mississippi",
abbreviation: "MS",
},
{
name: "Missouri",
abbreviation: "MO",
},
{
name: "Montana",
abbreviation: "MT",
},
{
name: "Nebraska",
abbreviation: "NE",
},
{
name: "Nevada",
abbreviation: "NV",
},
{
name: "New Hampshire",
abbreviation: "NH",
},
{
name: "New Jersey",
abbreviation: "NJ",
},
{
name: "New Mexico",
abbreviation: "NM",
},
{
name: "New York",
abbreviation: "NY",
},
{
name: "North Carolina",
abbreviation: "NC",
},
{
name: "North Dakota",
abbreviation: "ND",
},
{
name: "Northern Mariana Islands",
abbreviation: "MP",
},
{
name: "Ohio",
abbreviation: "OH",
},
{
name: "Oklahoma",
abbreviation: "OK",
},
{
name: "Oregon",
abbreviation: "OR",
},
{
name: "Pennsylvania",
abbreviation: "PA",
},
{
name: "Puerto Rico",
abbreviation: "PR",
},
{
name: "Rhode Island",
abbreviation: "RI",
},
{
name: "South Carolina",
abbreviation: "SC",
},
{
name: "South Dakota",
abbreviation: "SD",
},
{
name: "Tennessee",
abbreviation: "TN",
},
{
name: "Texas",
abbreviation: "TX",
},
{
name: "Utah",
abbreviation: "UT",
},
{
name: "Vermont",
abbreviation: "VT",
},
{
name: "Virgin Islands",
abbreviation: "VI",
},
{
name: "Virginia",
abbreviation: "VA",
},
{
name: "Washington",
abbreviation: "WA",
},
{
name: "West Virginia",
abbreviation: "WV",
},
{
name: "Wisconsin",
abbreviation: "WI",
},
{
name: "Wyoming",
abbreviation: "WY",
},
] as const;
export type StateName = (typeof states)[number]["name"];
export type StateAbbreviation = (typeof states)[number]["abbreviation"];
export interface State {
name: StateName;
abbreviation: StateAbbreviation;
}
Getting the current value
It is recommended to control the value instead of using this option.
When the value does not need to be stored anywhere but can be used for other
actions, the onValueChange prop can be used.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { type ReactElement } from "react";
export default function GettingTheCurrentValueExample(): ReactElement {
const defaultValue: State = states[9];
return (
<Autocomplete
label="State"
options={states}
defaultValue={defaultValue}
listboxLabel="States"
onValueChange={(value) => {
// Do something with the value. Should generally not call `setState`
// eslint-disable-next-line no-console
console.log("value:", value);
}}
/>
);
}
export const states = [
{
name: "Alabama",
abbreviation: "AL",
},
{
name: "Alaska",
abbreviation: "AK",
},
{
name: "American Samoa",
abbreviation: "AS",
},
{
name: "Arizona",
abbreviation: "AZ",
},
{
name: "Arkansas",
abbreviation: "AR",
},
{
name: "California",
abbreviation: "CA",
},
{
name: "Colorado",
abbreviation: "CO",
},
{
name: "Connecticut",
abbreviation: "CT",
},
{
name: "Delaware",
abbreviation: "DE",
},
{
name: "District Of Columbia",
abbreviation: "DC",
},
{
name: "Federated States Of Micronesia",
abbreviation: "FM",
},
{
name: "Florida",
abbreviation: "FL",
},
{
name: "Georgia",
abbreviation: "GA",
},
{
name: "Guam",
abbreviation: "GU",
},
{
name: "Hawaii",
abbreviation: "HI",
},
{
name: "Idaho",
abbreviation: "ID",
},
{
name: "Illinois",
abbreviation: "IL",
},
{
name: "Indiana",
abbreviation: "IN",
},
{
name: "Iowa",
abbreviation: "IA",
},
{
name: "Kansas",
abbreviation: "KS",
},
{
name: "Kentucky",
abbreviation: "KY",
},
{
name: "Louisiana",
abbreviation: "LA",
},
{
name: "Maine",
abbreviation: "ME",
},
{
name: "Marshall Islands",
abbreviation: "MH",
},
{
name: "Maryland",
abbreviation: "MD",
},
{
name: "Massachusetts",
abbreviation: "MA",
},
{
name: "Michigan",
abbreviation: "MI",
},
{
name: "Minnesota",
abbreviation: "MN",
},
{
name: "Mississippi",
abbreviation: "MS",
},
{
name: "Missouri",
abbreviation: "MO",
},
{
name: "Montana",
abbreviation: "MT",
},
{
name: "Nebraska",
abbreviation: "NE",
},
{
name: "Nevada",
abbreviation: "NV",
},
{
name: "New Hampshire",
abbreviation: "NH",
},
{
name: "New Jersey",
abbreviation: "NJ",
},
{
name: "New Mexico",
abbreviation: "NM",
},
{
name: "New York",
abbreviation: "NY",
},
{
name: "North Carolina",
abbreviation: "NC",
},
{
name: "North Dakota",
abbreviation: "ND",
},
{
name: "Northern Mariana Islands",
abbreviation: "MP",
},
{
name: "Ohio",
abbreviation: "OH",
},
{
name: "Oklahoma",
abbreviation: "OK",
},
{
name: "Oregon",
abbreviation: "OR",
},
{
name: "Pennsylvania",
abbreviation: "PA",
},
{
name: "Puerto Rico",
abbreviation: "PR",
},
{
name: "Rhode Island",
abbreviation: "RI",
},
{
name: "South Carolina",
abbreviation: "SC",
},
{
name: "South Dakota",
abbreviation: "SD",
},
{
name: "Tennessee",
abbreviation: "TN",
},
{
name: "Texas",
abbreviation: "TX",
},
{
name: "Utah",
abbreviation: "UT",
},
{
name: "Vermont",
abbreviation: "VT",
},
{
name: "Virgin Islands",
abbreviation: "VI",
},
{
name: "Virginia",
abbreviation: "VA",
},
{
name: "Washington",
abbreviation: "WA",
},
{
name: "West Virginia",
abbreviation: "WV",
},
{
name: "Wisconsin",
abbreviation: "WI",
},
{
name: "Wyoming",
abbreviation: "WY",
},
] as const;
export type StateName = (typeof states)[number]["name"];
export type StateAbbreviation = (typeof states)[number]["abbreviation"];
export interface State {
name: StateName;
abbreviation: StateAbbreviation;
}
Controlling the input value
If the input value needs to be controlled, provide a query and setQuery prop.
Query: ""
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { Box } from "@react-md/core/box/Box";
import { Button } from "@react-md/core/button/Button";
import { Typography } from "@react-md/core/typography/Typography";
import { type ReactElement, useState } from "react";
export default function ControllingTheInputValueExample(): ReactElement {
const [query, setQuery] = useState("");
return (
<Box align="start" stacked>
<Autocomplete
label="State"
query={query}
setQuery={setQuery}
options={states}
listboxLabel="States"
/>
<Button
onClick={() => {
setQuery("some other value");
}}
themeType="outline"
>
Set query
</Button>
<Typography>Query: {`"${query}"`}</Typography>
</Box>
);
}
export const states = [
{
name: "Alabama",
abbreviation: "AL",
},
{
name: "Alaska",
abbreviation: "AK",
},
{
name: "American Samoa",
abbreviation: "AS",
},
{
name: "Arizona",
abbreviation: "AZ",
},
{
name: "Arkansas",
abbreviation: "AR",
},
{
name: "California",
abbreviation: "CA",
},
{
name: "Colorado",
abbreviation: "CO",
},
{
name: "Connecticut",
abbreviation: "CT",
},
{
name: "Delaware",
abbreviation: "DE",
},
{
name: "District Of Columbia",
abbreviation: "DC",
},
{
name: "Federated States Of Micronesia",
abbreviation: "FM",
},
{
name: "Florida",
abbreviation: "FL",
},
{
name: "Georgia",
abbreviation: "GA",
},
{
name: "Guam",
abbreviation: "GU",
},
{
name: "Hawaii",
abbreviation: "HI",
},
{
name: "Idaho",
abbreviation: "ID",
},
{
name: "Illinois",
abbreviation: "IL",
},
{
name: "Indiana",
abbreviation: "IN",
},
{
name: "Iowa",
abbreviation: "IA",
},
{
name: "Kansas",
abbreviation: "KS",
},
{
name: "Kentucky",
abbreviation: "KY",
},
{
name: "Louisiana",
abbreviation: "LA",
},
{
name: "Maine",
abbreviation: "ME",
},
{
name: "Marshall Islands",
abbreviation: "MH",
},
{
name: "Maryland",
abbreviation: "MD",
},
{
name: "Massachusetts",
abbreviation: "MA",
},
{
name: "Michigan",
abbreviation: "MI",
},
{
name: "Minnesota",
abbreviation: "MN",
},
{
name: "Mississippi",
abbreviation: "MS",
},
{
name: "Missouri",
abbreviation: "MO",
},
{
name: "Montana",
abbreviation: "MT",
},
{
name: "Nebraska",
abbreviation: "NE",
},
{
name: "Nevada",
abbreviation: "NV",
},
{
name: "New Hampshire",
abbreviation: "NH",
},
{
name: "New Jersey",
abbreviation: "NJ",
},
{
name: "New Mexico",
abbreviation: "NM",
},
{
name: "New York",
abbreviation: "NY",
},
{
name: "North Carolina",
abbreviation: "NC",
},
{
name: "North Dakota",
abbreviation: "ND",
},
{
name: "Northern Mariana Islands",
abbreviation: "MP",
},
{
name: "Ohio",
abbreviation: "OH",
},
{
name: "Oklahoma",
abbreviation: "OK",
},
{
name: "Oregon",
abbreviation: "OR",
},
{
name: "Pennsylvania",
abbreviation: "PA",
},
{
name: "Puerto Rico",
abbreviation: "PR",
},
{
name: "Rhode Island",
abbreviation: "RI",
},
{
name: "South Carolina",
abbreviation: "SC",
},
{
name: "South Dakota",
abbreviation: "SD",
},
{
name: "Tennessee",
abbreviation: "TN",
},
{
name: "Texas",
abbreviation: "TX",
},
{
name: "Utah",
abbreviation: "UT",
},
{
name: "Vermont",
abbreviation: "VT",
},
{
name: "Virgin Islands",
abbreviation: "VI",
},
{
name: "Virginia",
abbreviation: "VA",
},
{
name: "Washington",
abbreviation: "WA",
},
{
name: "West Virginia",
abbreviation: "WV",
},
{
name: "Wisconsin",
abbreviation: "WI",
},
{
name: "Wyoming",
abbreviation: "WY",
},
] as const;
export type StateName = (typeof states)[number]["name"];
export type StateAbbreviation = (typeof states)[number]["abbreviation"];
export interface State {
name: StateName;
abbreviation: StateAbbreviation;
}
Getting the current input value
If the input value doesn't need to be controlled, the value can be retrieved
with the normal onChange event handler.
"use client";
/* eslint-disable no-console */
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { type ReactElement } from "react";
export default function GettingTheCurrentInputValueExample(): ReactElement {
return (
<Autocomplete
label="Fruit"
placeholder="Apple"
listboxLabel="Fruits"
options={fruits}
onChange={(event) => {
// do something with the current value
const { value } = event.currentTarget;
console.log("value: ", value);
}}
/>
);
}
export const fruits = [
"Apple",
"Apricot",
"Banana",
"Blueberry",
"Cranberry",
"Kiwi",
"Mango",
"Orange",
"Peach",
"Plum",
"Strawberry",
];
Disable Filtering
There are some cases where the available options should only be suggestions and
not required value such as displaying the most recent searches. Set the filter
prop to noopAutocompleteFilter to enable this behavior which also enables the
allowAnyValue prop.
This is the default behavior when type="search" is set.
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { noopAutocompleteFilter } from "@react-md/core/autocomplete/defaults";
import { type ReactElement } from "react";
export default function DisableFilteringExample(): ReactElement {
return (
<Autocomplete
label="Fruit"
placeholder="Apple"
listboxLabel="Fruits"
options={fruits}
filter={noopAutocompleteFilter}
/>
);
}
export const fruits = [
"Apple",
"Apricot",
"Banana",
"Blueberry",
"Cranberry",
"Kiwi",
"Mango",
"Orange",
"Peach",
"Plum",
"Strawberry",
];
Custom Filter Function
The Autocomplete uses the
caseInsensitiveSearch filter function by
default but can be changed using the filter prop. The filter function must
return the filtered list and is called with an object containing:
list- The list ofoptionsto filterquery- The current text field valueextractor- Theextractorfunction to get a string for each option
This example will show how the filter function could be swapped out for the fuzzySearch.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { type AutocompleteFilterFunction } from "@react-md/core/autocomplete/types";
import { caseInsensitiveSearch } from "@react-md/core/searching/caseInsensitive";
import { fuzzySearch } from "@react-md/core/searching/fuzzy";
import { type ReactElement } from "react";
const NO_STARTS_WITH = false;
const filter: AutocompleteFilterFunction<State> = (options) => {
// these are all provided
// const { list, query, extractor } = options
// The default filter behavior is to require each option to start with
// the query to match, so here's an example allow matching anywhere
if (NO_STARTS_WITH) {
return caseInsensitiveSearch(options);
}
// this is about the same as the caseInsensitiveSearch, but the letters
// do not need to appear next to each other to match
return fuzzySearch(options);
};
export default function CustomFilterFunctionExample(): ReactElement {
return (
<Autocomplete
label="State"
options={states}
listboxLabel="States"
filter={filter}
/>
);
}
export const states = [
{
name: "Alabama",
abbreviation: "AL",
},
{
name: "Alaska",
abbreviation: "AK",
},
{
name: "American Samoa",
abbreviation: "AS",
},
{
name: "Arizona",
abbreviation: "AZ",
},
{
name: "Arkansas",
abbreviation: "AR",
},
{
name: "California",
abbreviation: "CA",
},
{
name: "Colorado",
abbreviation: "CO",
},
{
name: "Connecticut",
abbreviation: "CT",
},
{
name: "Delaware",
abbreviation: "DE",
},
{
name: "District Of Columbia",
abbreviation: "DC",
},
{
name: "Federated States Of Micronesia",
abbreviation: "FM",
},
{
name: "Florida",
abbreviation: "FL",
},
{
name: "Georgia",
abbreviation: "GA",
},
{
name: "Guam",
abbreviation: "GU",
},
{
name: "Hawaii",
abbreviation: "HI",
},
{
name: "Idaho",
abbreviation: "ID",
},
{
name: "Illinois",
abbreviation: "IL",
},
{
name: "Indiana",
abbreviation: "IN",
},
{
name: "Iowa",
abbreviation: "IA",
},
{
name: "Kansas",
abbreviation: "KS",
},
{
name: "Kentucky",
abbreviation: "KY",
},
{
name: "Louisiana",
abbreviation: "LA",
},
{
name: "Maine",
abbreviation: "ME",
},
{
name: "Marshall Islands",
abbreviation: "MH",
},
{
name: "Maryland",
abbreviation: "MD",
},
{
name: "Massachusetts",
abbreviation: "MA",
},
{
name: "Michigan",
abbreviation: "MI",
},
{
name: "Minnesota",
abbreviation: "MN",
},
{
name: "Mississippi",
abbreviation: "MS",
},
{
name: "Missouri",
abbreviation: "MO",
},
{
name: "Montana",
abbreviation: "MT",
},
{
name: "Nebraska",
abbreviation: "NE",
},
{
name: "Nevada",
abbreviation: "NV",
},
{
name: "New Hampshire",
abbreviation: "NH",
},
{
name: "New Jersey",
abbreviation: "NJ",
},
{
name: "New Mexico",
abbreviation: "NM",
},
{
name: "New York",
abbreviation: "NY",
},
{
name: "North Carolina",
abbreviation: "NC",
},
{
name: "North Dakota",
abbreviation: "ND",
},
{
name: "Northern Mariana Islands",
abbreviation: "MP",
},
{
name: "Ohio",
abbreviation: "OH",
},
{
name: "Oklahoma",
abbreviation: "OK",
},
{
name: "Oregon",
abbreviation: "OR",
},
{
name: "Pennsylvania",
abbreviation: "PA",
},
{
name: "Puerto Rico",
abbreviation: "PR",
},
{
name: "Rhode Island",
abbreviation: "RI",
},
{
name: "South Carolina",
abbreviation: "SC",
},
{
name: "South Dakota",
abbreviation: "SD",
},
{
name: "Tennessee",
abbreviation: "TN",
},
{
name: "Texas",
abbreviation: "TX",
},
{
name: "Utah",
abbreviation: "UT",
},
{
name: "Vermont",
abbreviation: "VT",
},
{
name: "Virgin Islands",
abbreviation: "VI",
},
{
name: "Virginia",
abbreviation: "VA",
},
{
name: "Washington",
abbreviation: "WA",
},
{
name: "West Virginia",
abbreviation: "WV",
},
{
name: "Wisconsin",
abbreviation: "WI",
},
{
name: "Wyoming",
abbreviation: "WY",
},
] as const;
export type StateName = (typeof states)[number]["name"];
export type StateAbbreviation = (typeof states)[number]["abbreviation"];
export interface State {
name: StateName;
abbreviation: StateAbbreviation;
}
Allow Any Value
The Autocomplete normally behave like a searchable select component where it
requires a specific option to be selected and will reject all other values. If
any value should be allowed, enable the allowAnyValue prop.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { type ReactElement } from "react";
export default function AllowAnyValueExample(): ReactElement {
return (
<Autocomplete
label="Fruit"
placeholder="Apple"
listboxLabel="Fruits"
options={fruits}
allowAnyValue
/>
);
}
export const fruits = [
"Apple",
"Apricot",
"Banana",
"Blueberry",
"Cranberry",
"Kiwi",
"Mango",
"Orange",
"Peach",
"Plum",
"Strawberry",
];
Creatable
An alternative to the allowAnyValue prop is to allow the user to create a new
option with their typed value by manually selecting the dynamically generated
option. There is nothing built-in to the Autocomplete component itself, but
can be easily coded with a few changes:
- ensure the provided
optionsare a list of objects - provide a custom
filterfunction that inserts a new option at the end (or beginning) of the filtered items - optionally: also provide a custom
getOptionLabelfunction to handle the creatable item
This example will show how to insert a Add: "${query}" option with the list of
fruits. The Add: "${query}" is set as the children so that when the option
is selected, only the query will be inserted into the input.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { defaultAutocompleteFilter } from "@react-md/core/autocomplete/defaults";
import { type ReactElement } from "react";
export default function CreatableAutocompleteExample(): ReactElement {
return (
<Autocomplete
label="Fruit"
placeholder="Apple"
listboxLabel="Fruits"
options={fruits.map((fruit) => ({
label: fruit,
value: fruit,
}))}
filter={(options) => {
const { list, extractor, query } = options;
const filtered = defaultAutocompleteFilter(options);
if (query && !list.some((option) => extractor(option) === query)) {
return [
...filtered,
{
label: query,
value: query,
children: `Add: "${query}"`,
},
];
}
return filtered;
}}
/>
);
}
export const fruits = [
"Apple",
"Apricot",
"Banana",
"Blueberry",
"Cranberry",
"Kiwi",
"Mango",
"Orange",
"Peach",
"Plum",
"Strawberry",
];
Async Example
The Autocomplete supports rendering a CircularProgress after the input field to show
async behavior. The CircularProgress will be shown while the loading prop is true.
This example below will "load" the options each time the autocomplete is opened.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { useUnmounted } from "@react-md/core/useUnmounted";
import { wait } from "@react-md/core/utils/wait";
import { type ReactElement, useState } from "react";
interface AsyncState {
loading: boolean;
options: readonly State[];
}
export default function AsyncExample(): ReactElement {
const [{ loading, options }, setState] = useState<AsyncState>({
loading: false,
options: [],
});
const unmounted = useUnmounted();
return (
<Autocomplete
label="State"
options={options}
listboxLabel="States"
loading={loading}
onOpen={async () => {
setState({ loading: true, options: [] });
await wait(800);
if (!unmounted.current) {
setState({
loading: false,
options: states,
});
}
}}
style={{ width: "18rem" }}
/>
);
}
export const states = [
{
name: "Alabama",
abbreviation: "AL",
},
{
name: "Alaska",
abbreviation: "AK",
},
{
name: "American Samoa",
abbreviation: "AS",
},
{
name: "Arizona",
abbreviation: "AZ",
},
{
name: "Arkansas",
abbreviation: "AR",
},
{
name: "California",
abbreviation: "CA",
},
{
name: "Colorado",
abbreviation: "CO",
},
{
name: "Connecticut",
abbreviation: "CT",
},
{
name: "Delaware",
abbreviation: "DE",
},
{
name: "District Of Columbia",
abbreviation: "DC",
},
{
name: "Federated States Of Micronesia",
abbreviation: "FM",
},
{
name: "Florida",
abbreviation: "FL",
},
{
name: "Georgia",
abbreviation: "GA",
},
{
name: "Guam",
abbreviation: "GU",
},
{
name: "Hawaii",
abbreviation: "HI",
},
{
name: "Idaho",
abbreviation: "ID",
},
{
name: "Illinois",
abbreviation: "IL",
},
{
name: "Indiana",
abbreviation: "IN",
},
{
name: "Iowa",
abbreviation: "IA",
},
{
name: "Kansas",
abbreviation: "KS",
},
{
name: "Kentucky",
abbreviation: "KY",
},
{
name: "Louisiana",
abbreviation: "LA",
},
{
name: "Maine",
abbreviation: "ME",
},
{
name: "Marshall Islands",
abbreviation: "MH",
},
{
name: "Maryland",
abbreviation: "MD",
},
{
name: "Massachusetts",
abbreviation: "MA",
},
{
name: "Michigan",
abbreviation: "MI",
},
{
name: "Minnesota",
abbreviation: "MN",
},
{
name: "Mississippi",
abbreviation: "MS",
},
{
name: "Missouri",
abbreviation: "MO",
},
{
name: "Montana",
abbreviation: "MT",
},
{
name: "Nebraska",
abbreviation: "NE",
},
{
name: "Nevada",
abbreviation: "NV",
},
{
name: "New Hampshire",
abbreviation: "NH",
},
{
name: "New Jersey",
abbreviation: "NJ",
},
{
name: "New Mexico",
abbreviation: "NM",
},
{
name: "New York",
abbreviation: "NY",
},
{
name: "North Carolina",
abbreviation: "NC",
},
{
name: "North Dakota",
abbreviation: "ND",
},
{
name: "Northern Mariana Islands",
abbreviation: "MP",
},
{
name: "Ohio",
abbreviation: "OH",
},
{
name: "Oklahoma",
abbreviation: "OK",
},
{
name: "Oregon",
abbreviation: "OR",
},
{
name: "Pennsylvania",
abbreviation: "PA",
},
{
name: "Puerto Rico",
abbreviation: "PR",
},
{
name: "Rhode Island",
abbreviation: "RI",
},
{
name: "South Carolina",
abbreviation: "SC",
},
{
name: "South Dakota",
abbreviation: "SD",
},
{
name: "Tennessee",
abbreviation: "TN",
},
{
name: "Texas",
abbreviation: "TX",
},
{
name: "Utah",
abbreviation: "UT",
},
{
name: "Vermont",
abbreviation: "VT",
},
{
name: "Virgin Islands",
abbreviation: "VI",
},
{
name: "Virginia",
abbreviation: "VA",
},
{
name: "Washington",
abbreviation: "WA",
},
{
name: "West Virginia",
abbreviation: "WV",
},
{
name: "Wisconsin",
abbreviation: "WI",
},
{
name: "Wyoming",
abbreviation: "WY",
},
] as const;
export type StateName = (typeof states)[number]["name"];
export type StateAbbreviation = (typeof states)[number]["abbreviation"];
export interface State {
name: StateName;
abbreviation: StateAbbreviation;
}
Debounced Search Example
When the Autocomplete should be used as a <input type="search" /> to send
requests to an API as the user types to get the options, set the type="search"
and add a custom onChange handler to send the current value to the search API.
It is recommended to either debounce or throttle these search requests so that
each keystroke is not a new request.
react-md provides two hooks to help with debouncing and throttling:
This example will show a debounced implementation while the next example will show a throttled implementation.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { useDebouncedFunction } from "@react-md/core/useDebouncedFunction";
import { useUnmounted } from "@react-md/core/useUnmounted";
import { wait } from "@react-md/core/utils/wait";
import { type ReactElement, useRef, useState } from "react";
export default function DebouncedSearchExample(): ReactElement {
const [loading, setLoading] = useState(false);
const [options, setOptions] = useState<readonly string[]>([]);
const query = useRef("");
const unmounted = useUnmounted();
const search = useDebouncedFunction(async function load(
value: string,
): Promise<void> {
query.current = value;
await wait(1000);
if (!unmounted.current) {
setLoading(query.current !== value);
setOptions(
value.trim()
? Array.from({ length: 10 }, (_, i) => `${value} ${i + 1}`)
: [],
);
}
}, 500);
return (
<Autocomplete
type="search"
label="Search"
placeholder="Search..."
listboxLabel="Resultsk"
options={options}
loading={loading}
onChange={(event) => {
setLoading(true);
search(event.currentTarget.value);
}}
style={{ width: "18rem" }}
/>
);
}
Throttled Search Example
This is the same example as above, but using useThrottledFunction instead.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { useThrottledFunction } from "@react-md/core/useThrottledFunction";
import { useUnmounted } from "@react-md/core/useUnmounted";
import { wait } from "@react-md/core/utils/wait";
import { type ReactElement, useRef, useState } from "react";
export default function ThrottledSearchExample(): ReactElement {
const [loading, setLoading] = useState(false);
const [options, setOptions] = useState<readonly string[]>([]);
const query = useRef("");
const unmounted = useUnmounted();
const search = useThrottledFunction(async function load(
value: string,
): Promise<void> {
query.current = value;
await wait(1000);
if (!unmounted.current) {
setLoading(query.current !== value);
setOptions(
value.trim()
? Array.from({ length: 10 }, (_, i) => `${value} ${i + 1}`)
: [],
);
}
}, 300);
return (
<Autocomplete
type="search"
label="Search"
placeholder="Search..."
listboxLabel="Resultsk"
options={options}
loading={loading}
onChange={(event) => {
setLoading(true);
void search(event.currentTarget.value);
}}
style={{ width: "18rem" }}
/>
);
}
Multiple Values
The Autocomplete supports multiple values by either:
- setting the
defaultValueto a list - controlling the
valueand setting it to a list
When multiple values are allowed, the Autocomplete will display inline chips
representing all the selected values and automatically clear the input when a
new option is selected.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { type ReactElement, useState } from "react";
export default function MultipleValuesExample(): ReactElement {
const [value, setValue] = useState<readonly Dessert[]>([]);
return (
<>
<Autocomplete
label="Dessert"
placeholder="Ice cream"
value={value}
setValue={setValue}
listboxLabel="Desserts"
options={desserts}
/>
</>
);
}
export interface Dessert {
name: string;
calories: number;
fat: number;
carbs: number;
protein: number;
sodium: number;
calcium: number;
iron: number;
type: "Ice cream" | "Pastry" | "Other";
}
export type DessertKey = keyof Dessert;
export const desserts: readonly Dessert[] = [
{
name: "Frozen yogurt",
type: "Ice cream",
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
sodium: 87,
calcium: 14,
iron: 1,
},
{
name: "Ice cream sandwich",
type: "Ice cream",
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
sodium: 129,
calcium: 8,
iron: 1,
},
{
name: "Eclair",
type: "Pastry",
calories: 262,
fat: 16.0,
carbs: 37,
protein: 6.0,
sodium: 337,
calcium: 6,
iron: 7,
},
{
name: "Cupcake",
type: "Pastry",
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
sodium: 413,
calcium: 3,
iron: 8,
},
{
name: "Gingerbread",
type: "Pastry",
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
sodium: 327,
calcium: 7,
iron: 16,
},
{
name: "Jelly bean",
type: "Other",
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
sodium: 50,
calcium: 0,
iron: 0,
},
{
name: "Lollipop",
type: "Other",
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0.0,
sodium: 38,
calcium: 0,
iron: 2,
},
{
name: "Honeycomb",
type: "Other",
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
sodium: 562,
calcium: 0,
iron: 45,
},
{
name: "Donut",
type: "Pastry",
calories: 52,
fat: 25.0,
carbs: 51,
protein: 4.9,
sodium: 326,
calcium: 2,
iron: 22,
},
{
name: "KitKat",
type: "Other",
calories: 16,
fat: 6.0,
carbs: 65,
protein: 7.0,
sodium: 54,
calcium: 12,
iron: 6,
},
];
export const dessertColumns = Object.keys(desserts[0]) as readonly DessertKey[];
Disable Close On Select
To prevent the listbox from closing when an option is selected, enable the
disableCloseOnSelect prop.
This is also supported for the single select Autocomplete
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { type ReactElement, useState } from "react";
export default function DisableCloseOnSelectExample(): ReactElement {
const [value, setValue] = useState<readonly Dessert[]>([]);
return (
<>
<Autocomplete
label="Dessert"
placeholder="Ice cream"
value={value}
setValue={setValue}
listboxLabel="Desserts"
options={desserts}
disableCloseOnSelect
/>
</>
);
}
export interface Dessert {
name: string;
calories: number;
fat: number;
carbs: number;
protein: number;
sodium: number;
calcium: number;
iron: number;
type: "Ice cream" | "Pastry" | "Other";
}
export type DessertKey = keyof Dessert;
export const desserts: readonly Dessert[] = [
{
name: "Frozen yogurt",
type: "Ice cream",
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
sodium: 87,
calcium: 14,
iron: 1,
},
{
name: "Ice cream sandwich",
type: "Ice cream",
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
sodium: 129,
calcium: 8,
iron: 1,
},
{
name: "Eclair",
type: "Pastry",
calories: 262,
fat: 16.0,
carbs: 37,
protein: 6.0,
sodium: 337,
calcium: 6,
iron: 7,
},
{
name: "Cupcake",
type: "Pastry",
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
sodium: 413,
calcium: 3,
iron: 8,
},
{
name: "Gingerbread",
type: "Pastry",
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
sodium: 327,
calcium: 7,
iron: 16,
},
{
name: "Jelly bean",
type: "Other",
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
sodium: 50,
calcium: 0,
iron: 0,
},
{
name: "Lollipop",
type: "Other",
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0.0,
sodium: 38,
calcium: 0,
iron: 2,
},
{
name: "Honeycomb",
type: "Other",
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
sodium: 562,
calcium: 0,
iron: 45,
},
{
name: "Donut",
type: "Pastry",
calories: 52,
fat: 25.0,
carbs: 51,
protein: 4.9,
sodium: 326,
calcium: 2,
iron: 22,
},
{
name: "KitKat",
type: "Other",
calories: 16,
fat: 6.0,
carbs: 65,
protein: 7.0,
sodium: 54,
calcium: 12,
iron: 6,
},
];
export const dessertColumns = Object.keys(desserts[0]) as readonly DessertKey[];
Checkboxes
The multiselect Autocomplete also supports updating all the options to use
checkbox icons by enabling the checkboxes prop. This also enables the
disableCloseOnSelect by default.
The Option will use the ICON_CONFIG.checkbox and
ICON_CONFIG.checkboxChecked icons for the unselected and selected states.
These icons can be overridden using the getOptionProps and returning
a custom selectedIcon / unselectedIcon.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { type ReactElement, useState } from "react";
export default function MultiselectCheckboxesExample(): ReactElement {
const [value, setValue] = useState<readonly Dessert[]>([]);
return (
<>
<Autocomplete
label="Dessert"
placeholder="Ice cream"
value={value}
setValue={setValue}
listboxLabel="Desserts"
checkboxes
options={desserts}
/>
</>
);
}
export interface Dessert {
name: string;
calories: number;
fat: number;
carbs: number;
protein: number;
sodium: number;
calcium: number;
iron: number;
type: "Ice cream" | "Pastry" | "Other";
}
export type DessertKey = keyof Dessert;
export const desserts: readonly Dessert[] = [
{
name: "Frozen yogurt",
type: "Ice cream",
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
sodium: 87,
calcium: 14,
iron: 1,
},
{
name: "Ice cream sandwich",
type: "Ice cream",
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
sodium: 129,
calcium: 8,
iron: 1,
},
{
name: "Eclair",
type: "Pastry",
calories: 262,
fat: 16.0,
carbs: 37,
protein: 6.0,
sodium: 337,
calcium: 6,
iron: 7,
},
{
name: "Cupcake",
type: "Pastry",
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
sodium: 413,
calcium: 3,
iron: 8,
},
{
name: "Gingerbread",
type: "Pastry",
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
sodium: 327,
calcium: 7,
iron: 16,
},
{
name: "Jelly bean",
type: "Other",
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
sodium: 50,
calcium: 0,
iron: 0,
},
{
name: "Lollipop",
type: "Other",
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0.0,
sodium: 38,
calcium: 0,
iron: 2,
},
{
name: "Honeycomb",
type: "Other",
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
sodium: 562,
calcium: 0,
iron: 45,
},
{
name: "Donut",
type: "Pastry",
calories: 52,
fat: 25.0,
carbs: 51,
protein: 4.9,
sodium: 326,
calcium: 2,
iron: 22,
},
{
name: "KitKat",
type: "Other",
calories: 16,
fat: 6.0,
carbs: 65,
protein: 7.0,
sodium: 54,
calcium: 12,
iron: 6,
},
];
export const dessertColumns = Object.keys(desserts[0]) as readonly DessertKey[];
Disable Inline Chips
If the selected options should not be shown as inline chips, enable the
disableInlineChips prop.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { AutocompleteChip } from "@react-md/core/autocomplete/AutocompleteChip";
import { Box } from "@react-md/core/box/Box";
import { type ReactElement, useState } from "react";
export default function DisableInlineChipsExample(): ReactElement {
const [value, setValue] = useState<readonly Dessert[]>([
desserts[1],
desserts[2],
]);
return (
<>
<Box
role="status"
aria-live="polite"
aria-label="Selected Desserts"
align="start"
fullWidth
disablePadding
>
{value.map((dessert) => (
<AutocompleteChip
key={dessert.name}
theme="outline"
onClick={() => {
setValue((prevValue) => prevValue.filter((d) => d !== dessert));
}}
>
{dessert.name}
</AutocompleteChip>
))}
</Box>
<Autocomplete
label="Dessert"
placeholder="Ice cream"
value={value}
setValue={setValue}
listboxLabel="Desserts"
options={desserts}
disableInlineChips
/>
</>
);
}
export interface Dessert {
name: string;
calories: number;
fat: number;
carbs: number;
protein: number;
sodium: number;
calcium: number;
iron: number;
type: "Ice cream" | "Pastry" | "Other";
}
export type DessertKey = keyof Dessert;
export const desserts: readonly Dessert[] = [
{
name: "Frozen yogurt",
type: "Ice cream",
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
sodium: 87,
calcium: 14,
iron: 1,
},
{
name: "Ice cream sandwich",
type: "Ice cream",
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
sodium: 129,
calcium: 8,
iron: 1,
},
{
name: "Eclair",
type: "Pastry",
calories: 262,
fat: 16.0,
carbs: 37,
protein: 6.0,
sodium: 337,
calcium: 6,
iron: 7,
},
{
name: "Cupcake",
type: "Pastry",
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
sodium: 413,
calcium: 3,
iron: 8,
},
{
name: "Gingerbread",
type: "Pastry",
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
sodium: 327,
calcium: 7,
iron: 16,
},
{
name: "Jelly bean",
type: "Other",
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
sodium: 50,
calcium: 0,
iron: 0,
},
{
name: "Lollipop",
type: "Other",
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0.0,
sodium: 38,
calcium: 0,
iron: 2,
},
{
name: "Honeycomb",
type: "Other",
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
sodium: 562,
calcium: 0,
iron: 45,
},
{
name: "Donut",
type: "Pastry",
calories: 52,
fat: 25.0,
carbs: 51,
protein: 4.9,
sodium: 326,
calcium: 2,
iron: 22,
},
{
name: "KitKat",
type: "Other",
calories: 16,
fat: 6.0,
carbs: 65,
protein: 7.0,
sodium: 54,
calcium: 12,
iron: 6,
},
];
export const dessertColumns = Object.keys(desserts[0]) as readonly DessertKey[];
Filter Selected
The selected options can also be removed from the listbox by enabling the
filterSelected prop. It is only recommended to enable this prop when the
disableInlineChips is enabled.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { AutocompleteChip } from "@react-md/core/autocomplete/AutocompleteChip";
import { Box } from "@react-md/core/box/Box";
import { type ReactElement, useState } from "react";
export default function FilterSelectedExample(): ReactElement {
const [value, setValue] = useState<readonly Dessert[]>([
desserts[1],
desserts[2],
]);
return (
<>
<Box
role="status"
aria-live="polite"
aria-label="Selected Desserts"
align="start"
fullWidth
disablePadding
>
{value.map((dessert) => (
<AutocompleteChip
key={dessert.name}
theme="outline"
onClick={() => {
setValue((prevValue) => prevValue.filter((d) => d !== dessert));
}}
>
{dessert.name}
</AutocompleteChip>
))}
</Box>
<Autocomplete
label="Dessert"
placeholder="Ice cream"
value={value}
setValue={setValue}
listboxLabel="Desserts"
options={desserts}
filterSelected
disableInlineChips
/>
</>
);
}
export interface Dessert {
name: string;
calories: number;
fat: number;
carbs: number;
protein: number;
sodium: number;
calcium: number;
iron: number;
type: "Ice cream" | "Pastry" | "Other";
}
export type DessertKey = keyof Dessert;
export const desserts: readonly Dessert[] = [
{
name: "Frozen yogurt",
type: "Ice cream",
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
sodium: 87,
calcium: 14,
iron: 1,
},
{
name: "Ice cream sandwich",
type: "Ice cream",
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
sodium: 129,
calcium: 8,
iron: 1,
},
{
name: "Eclair",
type: "Pastry",
calories: 262,
fat: 16.0,
carbs: 37,
protein: 6.0,
sodium: 337,
calcium: 6,
iron: 7,
},
{
name: "Cupcake",
type: "Pastry",
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
sodium: 413,
calcium: 3,
iron: 8,
},
{
name: "Gingerbread",
type: "Pastry",
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
sodium: 327,
calcium: 7,
iron: 16,
},
{
name: "Jelly bean",
type: "Other",
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
sodium: 50,
calcium: 0,
iron: 0,
},
{
name: "Lollipop",
type: "Other",
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0.0,
sodium: 38,
calcium: 0,
iron: 2,
},
{
name: "Honeycomb",
type: "Other",
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
sodium: 562,
calcium: 0,
iron: 45,
},
{
name: "Donut",
type: "Pastry",
calories: 52,
fat: 25.0,
carbs: 51,
protein: 4.9,
sodium: 326,
calcium: 2,
iron: 22,
},
{
name: "KitKat",
type: "Other",
calories: 16,
fat: 6.0,
carbs: 65,
protein: 7.0,
sodium: 54,
calcium: 12,
iron: 6,
},
];
export const dessertColumns = Object.keys(desserts[0]) as readonly DessertKey[];
Customizing Inline Chips
The inline chips can be customized using the getChipProps which provides the
index and option being rendered. This can be used to disable chips, add
custom styles, change the theme, etc.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import FavoriteIcon from "@react-md/material-icons/FavoriteIcon";
import { type ReactElement } from "react";
export default function CustomizingInlineChipsExample(): ReactElement {
const isDisabled = (option: Dessert): boolean =>
option === desserts[0] || option === desserts[1] || option === desserts[4];
return (
<>
<Autocomplete
label="Dessert"
placeholder="Ice cream"
defaultValue={[desserts[0], desserts[1], desserts[4]]}
listboxLabel="Desserts"
options={desserts}
getOptionProps={({ option }) => {
return {
disabled: isDisabled(option),
};
}}
getChipProps={({ option, index }) => {
return {
theme: "outline",
leftAddon: <FavoriteIcon />,
disabled: isDisabled(option),
children: `${option.name} ${index + 1}`,
};
}}
/>
</>
);
}
export interface Dessert {
name: string;
calories: number;
fat: number;
carbs: number;
protein: number;
sodium: number;
calcium: number;
iron: number;
type: "Ice cream" | "Pastry" | "Other";
}
export type DessertKey = keyof Dessert;
export const desserts: readonly Dessert[] = [
{
name: "Frozen yogurt",
type: "Ice cream",
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
sodium: 87,
calcium: 14,
iron: 1,
},
{
name: "Ice cream sandwich",
type: "Ice cream",
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
sodium: 129,
calcium: 8,
iron: 1,
},
{
name: "Eclair",
type: "Pastry",
calories: 262,
fat: 16.0,
carbs: 37,
protein: 6.0,
sodium: 337,
calcium: 6,
iron: 7,
},
{
name: "Cupcake",
type: "Pastry",
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
sodium: 413,
calcium: 3,
iron: 8,
},
{
name: "Gingerbread",
type: "Pastry",
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
sodium: 327,
calcium: 7,
iron: 16,
},
{
name: "Jelly bean",
type: "Other",
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
sodium: 50,
calcium: 0,
iron: 0,
},
{
name: "Lollipop",
type: "Other",
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0.0,
sodium: 38,
calcium: 0,
iron: 2,
},
{
name: "Honeycomb",
type: "Other",
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
sodium: 562,
calcium: 0,
iron: 45,
},
{
name: "Donut",
type: "Pastry",
calories: 52,
fat: 25.0,
carbs: 51,
protein: 4.9,
sodium: 326,
calcium: 2,
iron: 22,
},
{
name: "KitKat",
type: "Other",
calories: 16,
fat: 6.0,
carbs: 65,
protein: 7.0,
sodium: 54,
calcium: 12,
iron: 6,
},
];
export const dessertColumns = Object.keys(desserts[0]) as readonly DessertKey[];
Customization
Highlights
The search query can be highlighted using the
HighlightText component and the getOptionProps
to provide highlighted children to each option.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { caseInsensitiveSearch } from "@react-md/core/searching/caseInsensitive";
import { HighlightText } from "@react-md/core/typography/HighlightText";
import { type ReactElement } from "react";
export default function HighlightsExample(): ReactElement {
return (
<Autocomplete
label="Fruit"
placeholder="Apple"
listboxLabel="Fruits"
options={desserts}
filter={(options) => caseInsensitiveSearch(options)}
getOptionProps={(options) => {
const { option, query } = options;
return {
children: <HighlightText query={query}>{option.name}</HighlightText>,
};
}}
/>
);
}
export interface Dessert {
name: string;
calories: number;
fat: number;
carbs: number;
protein: number;
sodium: number;
calcium: number;
iron: number;
type: "Ice cream" | "Pastry" | "Other";
}
export type DessertKey = keyof Dessert;
export const desserts: readonly Dessert[] = [
{
name: "Frozen yogurt",
type: "Ice cream",
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
sodium: 87,
calcium: 14,
iron: 1,
},
{
name: "Ice cream sandwich",
type: "Ice cream",
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
sodium: 129,
calcium: 8,
iron: 1,
},
{
name: "Eclair",
type: "Pastry",
calories: 262,
fat: 16.0,
carbs: 37,
protein: 6.0,
sodium: 337,
calcium: 6,
iron: 7,
},
{
name: "Cupcake",
type: "Pastry",
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
sodium: 413,
calcium: 3,
iron: 8,
},
{
name: "Gingerbread",
type: "Pastry",
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
sodium: 327,
calcium: 7,
iron: 16,
},
{
name: "Jelly bean",
type: "Other",
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
sodium: 50,
calcium: 0,
iron: 0,
},
{
name: "Lollipop",
type: "Other",
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0.0,
sodium: 38,
calcium: 0,
iron: 2,
},
{
name: "Honeycomb",
type: "Other",
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
sodium: 562,
calcium: 0,
iron: 45,
},
{
name: "Donut",
type: "Pastry",
calories: 52,
fat: 25.0,
carbs: 51,
protein: 4.9,
sodium: 326,
calcium: 2,
iron: 22,
},
{
name: "KitKat",
type: "Other",
calories: 16,
fat: 6.0,
carbs: 65,
protein: 7.0,
sodium: 54,
calcium: 12,
iron: 6,
},
];
export const dessertColumns = Object.keys(desserts[0]) as readonly DessertKey[];
Disabling the Clear and Dropdown Buttons
The clear button and dropdown button can be removed by enabling the
disableClearButton and disableDropdownButton props respectively. They do not
need to be used in tandem.
The keyboard shortcuts for clearing the input and showing/hiding the menu will still be enabled if these props are enabled.
"use client";
import { Autocomplete } from "@react-md/core/autocomplete/Autocomplete";
import { type ReactElement } from "react";
export default function DisablingTheClearAndDropdownButtonsExample(): ReactElement {
return (
<Autocomplete
label="Fruit"
placeholder="Apple"
listboxLabel="Fruits"
options={fruits}
disableClearButton
disableDropdownButton
/>
);
}
export const fruits = [
"Apple",
"Apricot",
"Banana",
"Blueberry",
"Cranberry",
"Kiwi",
"Mango",
"Orange",
"Peach",
"Plum",
"Strawberry",
];
Github's Label Picker
This is a more complex example that will showcase most of the
customization options available in the Autocomplete component and other
child components.
This demo shows how the Github label picker could be created with react-md
with most of the same behavior. Here's a breakdown of how this was created:
- Start by creating a simple
Button+FixedDialogcombination to display theAutocomplete - Create a multiselect
Autocompletewith most of the default UI disabled to better match GithubdisableClearButtondisableDropdownButtondisableInlineChipslistboxProps.disableElevation
- Update the
Autocompleteso that the listbox renders inline with the other content and within the dialoglistboxProps.disablePortallistboxProps.disableFixedPositioning
- Ensure the listbox is always visible by enabling the
visibleprop and providing a noop for thesetVisibleprop. Also enable thedisableCloseOnSelectprop to ensure items are still filtered after selecting an new value - Set
updateQueryOnSelectto"as-is"so that selecting a new value doesn't change the value in theAutocomplete - Update the behavior so that the selected values are only updated when the
dialog closes by:
- creating a
nextValueref that is mutated with theonValueChangeprop - setting the
defaultValueto thelabelsstate which ensures the latest values are selected each time the dialog is opened - add an
onExitedhandler to theDialogthat sets the state with thenextValueref
- creating a
- Allow filtering to happen anywhere within the label name by setting
filter={(options) => caseInsensitiveSearch(options)} - Sort the
optionsprop for theAutocompleteso that the selected labels appear first - Provide a
onKeyDownhandler for theDialog,Listbox, andinputthat closes the dialog when the Escape key is pressed - Render the selected labels in a
BoxwithChipcomponents - Update any styles to match Github
None yet
Typescript Typing
The Autocomplete option is defined as:
type AutomaticTextExtraction = string | { label: string } | { name: string };
type AutocompleteOption = AutomaticTextExtraction | object;
Each option should extend this type and will automatically be inferred for almost all use cases.
// ✅ Inferred as `string[]`
<Autocomplete {...props} options={["One", "Two", "Three"]} />;
// ✅ Inferred as `string[]`
const options = ["One", "Two", "Three"];
<Autocomplete {...props} options={options} />;
// ✅ Inferred as `("One" | "Two" | "Three")[]`
const options = ["One", "Two", "Three"] as const;
<Autocomplete {...props} options={options} />;
// ✅ Inferred as `{ name: string, value: number }`
const options = [
{ name: "Hello", value: 1 },
{ name: "World", value: 2 },
];
<Autocomplete
{...props}
options={options}
/>;
// ✅ Inferred as `State`
interface State {
name: string;
abbreviation: string;
}
const options: readonly State[] = [
{ name: "Virginia", abbreviation: "VA" },
{ name: "Whyoming", abbreviation: "WY" },
];
<Autocomplete
{...props}
options={options}
/>;
The only time it appears to have an issue is when the list of options are
defined with as const:
const options = [
{ fullName: "Virginia", abbreviation: "VA" },
{ fullName: "Whyoming", abbreviation: "WY" },
] as const;
<Autocomplete
{...props}
options={options}
// ❌ Parameter 'state' implicity has an `any` type.
getOptionLabel={(state) => state.fullName}
/>;
The type issue can be resolved by one of the following:
const options = [
{ fullName: "Virginia", abbreviation: "VA" },
{ fullName: "Whyoming", abbreviation: "WY" },
] as const;
type State = (typeof options)[number];
// 1. Provide the type definition to the `getOptionLabel` parameter
<Autocomplete
{...props}
// ✅ `state` and `options` are set to the `State` type
options={options}
getOptionLabel={(state: State) => state.fullName}
/>;
// 2. Provide the type definition as the `Autocomplete` type parameter
<Autocomplete<State>
{...props}
// ✅ `state` and `options` are set to the `State` type
options={options}
getOptionLabel={(state) => state.fullName}
/>;
Accessibility
The Autocomplete component implements the
combobox
so the <input /> will remain focused while the options gain styles to show
they are focused.
Labels
- The listbox requires an
aria-label/aria-labelledbyby thelistboxLabel/listboxLabelledbyprops respectively - The dropdown button defaults to an
aria-label/aria-labelledbybased on thelistboxLabel/listboxLabelledbyprops respectively - The inline circular progress bar defaults to an
aria-label="Loading"
Keyboard Movement
The following keyboard movement has been implemented:
Closed Listbox
- Typing will open the listbox and keep focus on the input.
- ArrowDown - Opens the listbox and focuses the first option.
- Alt + ArrowDown - Opens the listbox and keeps focus on the input.
- ArrowUp - Opens the listbox and focuses the last option.
- Escape - Clears the value.
- Home/End - Moves cursor to the start and end of the input
Open Listbox
- Typing will move focus back to the input
- ArrowDown - Moves focus to the next option in the listbox. If focus was on the input, the first option will be focused.
- ArrowUp - Moves focus to the previous option in the listbox. If focus was on the input, the last option will be focused.
- Alt + ArrowUp - If an option is focused, moves focus into the input. If the input is focused, closes the listbox.
- ArrowLeft/ArrowRight/Home/End - Moves focus into the input and uses default text editing behavior for these keys.
- Escape - Hides the listbox.