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 ofoptions
to filterquery
- The current text field valueextractor
- Theextractor
function 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
options
are a list of objects - provide a custom
filter
function that inserts a new option at the end (or beginning) of the filtered items - optionally: also provide a custom
getOptionLabel
function 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
defaultValue
to a list - controlling the
value
and 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
+FixedDialog
combination to display theAutocomplete
- Create a multiselect
Autocomplete
with most of the default UI disabled to better match GithubdisableClearButton
disableDropdownButton
disableInlineChips
listboxProps.disableElevation
- Update the
Autocomplete
so that the listbox renders inline with the other content and within the dialoglistboxProps.disablePortal
listboxProps.disableFixedPositioning
- Ensure the listbox is always visible by enabling the
visible
prop and providing a noop for thesetVisible
prop. Also enable thedisableCloseOnSelect
prop to ensure items are still filtered after selecting an new value - Set
updateQueryOnSelect
to"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
nextValue
ref that is mutated with theonValueChange
prop - setting the
defaultValue
to thelabels
state which ensures the latest values are selected each time the dialog is opened - add an
onExited
handler to theDialog
that sets the state with thenextValue
ref
- creating a
- Allow filtering to happen anywhere within the label name by setting
filter={(options) => caseInsensitiveSearch(options)}
- Sort the
options
prop for theAutocomplete
so that the selected labels appear first - Provide a
onKeyDown
handler for theDialog
,Listbox
, andinput
that closes the dialog when the Escape key is pressed - Render the selected labels in a
Box
withChip
components - 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-labelledby
by thelistboxLabel
/listboxLabelledby
props respectively - The dropdown button defaults to an
aria-label
/aria-labelledby
based on thelistboxLabel
/listboxLabelledby
props 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.