useSearchAutocomplete
Provides the behavior and accessibility implementation for a search autocomplete component. A search autocomplete combines a combobox with a searchfield, allowing users to filter a list of options to items matching a query.
install | yarn add @react-aria/autocomplete |
---|---|
version | 3.0.0-alpha.1 |
usage | import {useSearchAutocomplete} from '@react-aria/autocomplete' |
API#
useSearchAutocomplete<T>(
(props: AriaSearchAutocompleteProps<T>,
, state: ComboBoxState<T>
)): SearchAutocompleteAria<T>
Features#
Autocomplete for search fields can be implemented using the <datalist> HTML element, but this has limited functionality and behaves differently across browsers.
useSearchAutocomplete
helps achieve accessible search field and autocomplete components that can be styled as needed.
- Support for filtering a list of options by typing
- Support for selecting a single option
- Support for disabled options
- Support for groups of items in sections
- Support for custom user input values
- Support for controlled and uncontrolled options, selection, input value, and open state
- Support for custom filter functions
- Async loading and infinite scrolling support
- Support for virtualized scrolling for performance with long lists
- Exposed to assistive technology as a
combobox
with ARIA - Labeling support for accessibility
- Required and invalid states exposed to assistive technology via ARIA
- Support for mouse, touch, and keyboard interactions
- Keyboard support for opening the list box using the arrow keys, including automatically focusing the first or last item accordingly
- Support for opening the list box when typing, on focus, or manually
- Handles virtual clicks on the input from touch screen readers to toggle the list box
- Virtual focus management for list box option navigation
- Hides elements outside the input and list box from assistive technology while the list box is open in a portal
- Custom localized announcements for option focusing, filtering, and selection using an ARIA live region to work around VoiceOver bugs
Anatomy#
A search autocomplete consists of a label, an input which displays the current value, and a list box popup. Users can type within the input
to see search suggestions within the list box. The list box popup may be opened by a variety of input field interactions specified
by the menuTrigger
prop provided to useSearchAutocomplete
. useSearchAutocomplete
handles exposing
the correct ARIA attributes for accessibility for each of the elements comprising the search autocomplete. It should be combined
with useListBox, which handles the implementation of the popup list box.
useSearchAutocomplete
returns props that you should spread onto the appropriate elements:
Name | Type | Description |
labelProps | HTMLAttributes<HTMLElement> | Props for the label element. |
inputProps | InputHTMLAttributes<HTMLInputElement> | Props for the search input element. |
listBoxProps | AriaListBoxOptions<T> | Props for the list box, to be passed to useListBox. |
clearButtonProps | AriaButtonProps | Props for the search input's clear button. |
State is managed by the useComboBoxState
hook from @react-stately/combobox
.
The state object should be passed as an option to useSearchAutocomplete
.
If the search field does not have a visible label, an aria-label
or aria-labelledby
prop must be provided instead to
identify it to assistive technology.
State management#
useSearchAutocomplete
requires knowledge of the options in order to handle keyboard
navigation and other interactions. It does this using the Collection
interface, which is a generic interface to access sequential unique keyed data. You can
implement this interface yourself, e.g. by using a prop to pass a list of item objects,
but useComboBoxState
from
@react-stately/combobox
implements a JSX based interface for building collections instead.
See Collection Components for more information,
and Collection Interface for internal details.
In addition, useComboBoxState
manages the state necessary for single selection and exposes
a SelectionManager
,
which makes use of the collection to provide an interface to update the selection state.
It also holds state to track if the popup is open, if the search field is focused, and the current input value.
For more information about selection, see Selection.
Example#
This example uses an <input>
element for the search field.
A "contains" filter function is obtained from useFilter
and is passed to useComboBoxState
so
that the list box can be filtered based on the option text and the current input text.
The list box popup should use the same Popover
and ListBox
components created with useOverlay
and useListBox that you may already have in your component library or application. These can be shared with other
components such as a Select
created with useSelect or a Dialog
popover created with useDialog.
The code for these components is also included below in the collapsed sections.
This example does not do any advanced popover positioning or portaling to escape its visual container.
See useOverlayTrigger for an example of how to implement this
using useOverlayPosition
.
In addition, see useListBox for examples of sections (option groups), and more complex options.
import {Item} from '@react-stately/collections';
import {useButton} from '@react-aria/button';
import {useComboBoxState} from '@react-stately/combobox';
import {useSearchAutocomplete} from '@react-aria/autocomplete';
import {useFilter} from '@react-aria/i18n';
// Reuse the ListBox and Popover from your component library. See below for details.
import {ListBox Popover} from 'your-component-library';
function SearchAutocomplete(props) {
// Setup filter function and state.
let {contains} = useFilter({sensitivity: 'base'});
let state = useComboBoxState({...props defaultFilter: contains});
// Setup refs and get props for child elements.
let inputRef = ReactuseRef(null);
let listBoxRef = ReactuseRef(null);
let popoverRef = ReactuseRef(null);
let {
inputProps
listBoxProps
labelProps
clearButtonProps
} = useSearchAutocomplete(
{
...props
popoverRef
listBoxRef
inputRef
}
state
);
let {buttonProps} = useButton(clearButtonProps);
return (
<div style={display: 'inline-flex' flexDirection: 'column'}>
<label ...labelProps> propslabel</label>
<div style={position: 'relative' display: 'inline-block'}>
<input
...inputProps
ref= inputRef
style={
height: 24
boxSizing: 'border-box'
marginRight: 0
fontSize: 16
}
/>
stateinputValue !== '' && <button ...buttonProps>❎</button>
stateisOpen && (
<Popover
popoverRef= popoverRef
isOpen= stateisOpen
onClose= stateclose>
<ListBox ...listBoxProps listBoxRef= listBoxRef state= state />
</Popover>
)
</div>
</div>
);
}
<SearchAutocomplete label="Search Animals">
<Item key="red panda">Red Panda</Item>
<Item key="cat">Cat</Item>
<Item key="dog">Dog</Item>
<Item key="aardvark">Aardvark</Item>
<Item key="kangaroo">Kangaroo</Item>
<Item key="snake">Snake</Item>
</SearchAutocomplete>
import {Item} from '@react-stately/collections';
import {useButton} from '@react-aria/button';
import {useComboBoxState} from '@react-stately/combobox';
import {useSearchAutocomplete} from '@react-aria/autocomplete';
import {useFilter} from '@react-aria/i18n';
// Reuse the ListBox and Popover from your component library. See below for details.
import {ListBox Popover} from 'your-component-library';
function SearchAutocomplete(props) {
// Setup filter function and state.
let {contains} = useFilter({sensitivity: 'base'});
let state = useComboBoxState({
...props
defaultFilter: contains
});
// Setup refs and get props for child elements.
let inputRef = ReactuseRef(null);
let listBoxRef = ReactuseRef(null);
let popoverRef = ReactuseRef(null);
let {
inputProps
listBoxProps
labelProps
clearButtonProps
} = useSearchAutocomplete(
{
...props
popoverRef
listBoxRef
inputRef
}
state
);
let {buttonProps} = useButton(clearButtonProps);
return (
<div
style={
display: 'inline-flex'
flexDirection: 'column'
}>
<label ...labelProps> propslabel</label>
<div
style={
position: 'relative'
display: 'inline-block'
}>
<input
...inputProps
ref= inputRef
style={
height: 24
boxSizing: 'border-box'
marginRight: 0
fontSize: 16
}
/>
stateinputValue !== '' && (
<button ...buttonProps>❎</button>
)
stateisOpen && (
<Popover
popoverRef= popoverRef
isOpen= stateisOpen
onClose= stateclose>
<ListBox
...listBoxProps
listBoxRef= listBoxRef
state= state
/>
</Popover>
)
</div>
</div>
);
}
<SearchAutocomplete label="Search Animals">
<Item key="red panda">Red Panda</Item>
<Item key="cat">Cat</Item>
<Item key="dog">Dog</Item>
<Item key="aardvark">Aardvark</Item>
<Item key="kangaroo">Kangaroo</Item>
<Item key="snake">Snake</Item>
</SearchAutocomplete>
import {Item} from '@react-stately/collections';
import {useButton} from '@react-aria/button';
import {useComboBoxState} from '@react-stately/combobox';
import {useSearchAutocomplete} from '@react-aria/autocomplete';
import {useFilter} from '@react-aria/i18n';
// Reuse the ListBox and Popover from your component library. See below for details.
import {
ListBox
Popover
} from 'your-component-library';
function SearchAutocomplete(
props
) {
// Setup filter function and state.
let {
contains
} = useFilter({
sensitivity: 'base'
});
let state = useComboBoxState(
{
...props
defaultFilter: contains
}
);
// Setup refs and get props for child elements.
let inputRef = ReactuseRef(
null
);
let listBoxRef = ReactuseRef(
null
);
let popoverRef = ReactuseRef(
null
);
let {
inputProps
listBoxProps
labelProps
clearButtonProps
} = useSearchAutocomplete(
{
...props
popoverRef
listBoxRef
inputRef
}
state
);
let {
buttonProps
} = useButton(
clearButtonProps
);
return (
<div
style={
display:
'inline-flex'
flexDirection:
'column'
}>
<label
...labelProps>
propslabel
</label>
<div
style={
position:
'relative'
display:
'inline-block'
}>
<input
...inputProps
ref= inputRef
style={
height: 24
boxSizing:
'border-box'
marginRight: 0
fontSize: 16
}
/>
stateinputValue !==
'' && (
<button
...buttonProps>
❎
</button>
)
stateisOpen && (
<Popover
popoverRef=
popoverRef
isOpen=
stateisOpen
onClose=
stateclose
>
<ListBox
...listBoxProps
listBoxRef=
listBoxRef
state=
state
/>
</Popover>
)
</div>
</div>
);
}
<SearchAutocomplete label="Search Animals">
<Item key="red panda">
Red Panda
</Item>
<Item key="cat">
Cat
</Item>
<Item key="dog">
Dog
</Item>
<Item key="aardvark">
Aardvark
</Item>
<Item key="kangaroo">
Kangaroo
</Item>
<Item key="snake">
Snake
</Item>
</SearchAutocomplete>
Popover#
The Popover
component is used to contain the popup listbox for the SearchAutocomplete.
It can be shared between many other components, including Select,
Menu, Dialog, and others.
See useOverlayTrigger for more examples of popovers.
Show code
import {useOverlay DismissButton} from '@react-aria/overlays';
import {FocusScope} from '@react-aria/focus';
function Popover(props) {
let ref = ReactuseRef();
let {popoverRef = ref isOpen onClose children} = props;
// Handle events that should cause the popup to close,
// e.g. blur, clicking outside, or pressing the escape key.
let {overlayProps} = useOverlay(
{
isOpen
onClose
shouldCloseOnBlur: true
isDismissable: true
}
popoverRef
);
// Add a hidden <DismissButton> component at the end of the popover
// to allow screen reader users to dismiss the popup easily.
return (
<FocusScope restoreFocus>
<div
...overlayProps
ref= popoverRef
style={
position: 'absolute'
width: '100%'
border: '1px solid gray'
background: 'lightgray'
marginTop: 4
}>
children
<DismissButton onDismiss= onClose />
</div>
</FocusScope>
);
}
import {
useOverlay
DismissButton
} from '@react-aria/overlays';
import {FocusScope} from '@react-aria/focus';
function Popover(props) {
let ref = ReactuseRef();
let {popoverRef = ref isOpen onClose children} = props;
// Handle events that should cause the popup to close,
// e.g. blur, clicking outside, or pressing the escape key.
let {overlayProps} = useOverlay(
{
isOpen
onClose
shouldCloseOnBlur: true
isDismissable: true
}
popoverRef
);
// Add a hidden <DismissButton> component at the end of the popover
// to allow screen reader users to dismiss the popup easily.
return (
<FocusScope restoreFocus>
<div
...overlayProps
ref= popoverRef
style={
position: 'absolute'
width: '100%'
border: '1px solid gray'
background: 'lightgray'
marginTop: 4
}>
children
<DismissButton onDismiss= onClose />
</div>
</FocusScope>
);
}
import {
useOverlay
DismissButton
} from '@react-aria/overlays';
import {FocusScope} from '@react-aria/focus';
function Popover(props) {
let ref = ReactuseRef();
let {
popoverRef = ref
isOpen
onClose
children
} = props;
// Handle events that should cause the popup to close,
// e.g. blur, clicking outside, or pressing the escape key.
let {
overlayProps
} = useOverlay(
{
isOpen
onClose
shouldCloseOnBlur: true
isDismissable: true
}
popoverRef
);
// Add a hidden <DismissButton> component at the end of the popover
// to allow screen reader users to dismiss the popup easily.
return (
<FocusScope
restoreFocus>
<div
...overlayProps
ref= popoverRef
style={
position:
'absolute'
width: '100%'
border:
'1px solid gray'
background:
'lightgray'
marginTop: 4
}>
children
<DismissButton
onDismiss=
onClose
/>
</div>
</FocusScope>
);
}
ListBox#
The ListBox
and Option
components are used to show the filtered list of options as the
user types in the SearchAutocomplete. They can also be shared with other components like a Select. See
useListBox for more examples, including sections and more complex items.
Show code
import {useListBox useOption} from '@react-aria/listbox';
function ListBox(props) {
let ref = ReactuseRef();
let {listBoxRef = ref state} = props;
let {listBoxProps} = useListBox(props state listBoxRef);
return (
<ul
...listBoxProps
ref= listBoxRef
style={
margin: 0
padding: 0
listStyle: 'none'
maxHeight: '150px'
overflow: 'auto'
}>
[...statecollection]map((item) => (
<Option key= itemkey item= item state= state />
))
</ul>
);
}
function Option({item state}) {
let ref = ReactuseRef();
let {optionProps isSelected isFocused isDisabled} = useOption(
{key: itemkey}
state
ref
);
let backgroundColor;
let color = 'black';
if (isSelected) {
backgroundColor = 'blueviolet';
color = 'white';
} else if (isFocused) {
backgroundColor = 'gray';
} else if (isDisabled) {
backgroundColor = 'transparent';
color = 'gray';
}
return (
<li
...optionProps
ref= ref
style={
background: backgroundColor
color: color
padding: '2px 5px'
outline: 'none'
cursor: 'pointer'
}>
itemrendered
</li>
);
}
import {useListBox useOption} from '@react-aria/listbox';
function ListBox(props) {
let ref = ReactuseRef();
let {listBoxRef = ref state} = props;
let {listBoxProps} = useListBox(props state listBoxRef);
return (
<ul
...listBoxProps
ref= listBoxRef
style={
margin: 0
padding: 0
listStyle: 'none'
maxHeight: '150px'
overflow: 'auto'
}>
[...statecollection]map((item) => (
<Option key= itemkey item= item state= state />
))
</ul>
);
}
function Option({item state}) {
let ref = ReactuseRef();
let {
optionProps
isSelected
isFocused
isDisabled
} = useOption({key: itemkey} state ref);
let backgroundColor;
let color = 'black';
if (isSelected) {
backgroundColor = 'blueviolet';
color = 'white';
} else if (isFocused) {
backgroundColor = 'gray';
} else if (isDisabled) {
backgroundColor = 'transparent';
color = 'gray';
}
return (
<li
...optionProps
ref= ref
style={
background: backgroundColor
color: color
padding: '2px 5px'
outline: 'none'
cursor: 'pointer'
}>
itemrendered
</li>
);
}
import {
useListBox
useOption
} from '@react-aria/listbox';
function ListBox(props) {
let ref = ReactuseRef();
let {
listBoxRef = ref
state
} = props;
let {
listBoxProps
} = useListBox(
props
state
listBoxRef
);
return (
<ul
...listBoxProps
ref= listBoxRef
style={
margin: 0
padding: 0
listStyle:
'none'
maxHeight:
'150px'
overflow: 'auto'
}>
[
...statecollection
]map((item) => (
<Option
key= itemkey
item= item
state= state
/>
))
</ul>
);
}
function Option({
item
state
}) {
let ref = ReactuseRef();
let {
optionProps
isSelected
isFocused
isDisabled
} = useOption(
{key: itemkey}
state
ref
);
let backgroundColor;
let color = 'black';
if (isSelected) {
backgroundColor =
'blueviolet';
color = 'white';
} else if (isFocused) {
backgroundColor =
'gray';
} else if (
isDisabled
) {
backgroundColor =
'transparent';
color = 'gray';
}
return (
<li
...optionProps
ref= ref
style={
background: backgroundColor
color: color
padding:
'2px 5px'
outline: 'none'
cursor: 'pointer'
}>
itemrendered
</li>
);
}
Usage#
The following examples show how to use the SearchAutocomplete component created in the above example.
Uncontrolled#
The following example shows how you would create an uncontrolled SearchAutocomplete. The input value, selected option, and open state is completely uncontrolled.
<SearchAutocomplete label="Search Animals">
<Item key="red panda">Red Panda</Item>
<Item key="cat">Cat</Item>
<Item key="dog">Dog</Item>
<Item key="aardvark">Aardvark</Item>
<Item key="kangaroo">Kangaroo</Item>
<Item key="snake">Snake</Item>
</SearchAutocomplete>
<SearchAutocomplete label="Search Animals">
<Item key="red panda">Red Panda</Item>
<Item key="cat">Cat</Item>
<Item key="dog">Dog</Item>
<Item key="aardvark">Aardvark</Item>
<Item key="kangaroo">Kangaroo</Item>
<Item key="snake">Snake</Item>
</SearchAutocomplete>
<SearchAutocomplete label="Search Animals">
<Item key="red panda">
Red Panda
</Item>
<Item key="cat">
Cat
</Item>
<Item key="dog">
Dog
</Item>
<Item key="aardvark">
Aardvark
</Item>
<Item key="kangaroo">
Kangaroo
</Item>
<Item key="snake">
Snake
</Item>
</SearchAutocomplete>
Dynamic collections#
SearchAutocomplete follows the Collection Components API, accepting both static and dynamic collections. The examples above show static collections, which can be used when the full list of options is known ahead of time. Dynamic collections, as shown below, can be used when the options come from an external data source such as an API call, or update over time.
As seen below, an iterable list of options is passed to the SearchAutocomplete using the defaultItems
prop. The input's value is passed to the
onSubmit
handler, along with a key if the event was triggered by selecting an item from the listbox.
function Example() {
let options = [
{id: 1 name: 'Aerospace'}
{id: 2 name: 'Mechanical'}
{id: 3 name: 'Civil'}
{id: 4 name: 'Biomedical'}
{id: 5 name: 'Nuclear'}
{id: 6 name: 'Industrial'}
{id: 7 name: 'Chemical'}
{id: 8 name: 'Agricultural'}
{id: 9 name: 'Electrical'}
];
let [major setMajor] = ReactuseState();
let onSubmit = (value key) => {
if (value) {
setMajor(value);
} else if (key) {
setMajor(optionsfind((o) => oid === key)name);
}
};
return (
<>
<SearchAutocomplete
label="Search engineering majors"
defaultItems= options
onSubmit= onSubmit>
(item) => <Item> itemname</Item>
</SearchAutocomplete>
<p>Results for: major</p>
</>
);
}
function Example() {
let options = [
{id: 1 name: 'Aerospace'}
{id: 2 name: 'Mechanical'}
{id: 3 name: 'Civil'}
{id: 4 name: 'Biomedical'}
{id: 5 name: 'Nuclear'}
{id: 6 name: 'Industrial'}
{id: 7 name: 'Chemical'}
{id: 8 name: 'Agricultural'}
{id: 9 name: 'Electrical'}
];
let [major setMajor] = ReactuseState();
let onSubmit = (value key) => {
if (value) {
setMajor(value);
} else if (key) {
setMajor(optionsfind((o) => oid === key)name);
}
};
return (
<>
<SearchAutocomplete
label="Search engineering majors"
defaultItems= options
onSubmit= onSubmit>
(item) => <Item> itemname</Item>
</SearchAutocomplete>
<p>Results for: major</p>
</>
);
}
function Example() {
let options = [
{
id: 1
name: 'Aerospace'
}
{
id: 2
name: 'Mechanical'
}
{
id: 3
name: 'Civil'
}
{
id: 4
name: 'Biomedical'
}
{
id: 5
name: 'Nuclear'
}
{
id: 6
name: 'Industrial'
}
{
id: 7
name: 'Chemical'
}
{
id: 8
name:
'Agricultural'
}
{
id: 9
name: 'Electrical'
}
];
let [
major
setMajor
] = ReactuseState();
let onSubmit = (
value
key
) => {
if (value) {
setMajor(value);
} else if (key) {
setMajor(
optionsfind(
(o) =>
oid === key
)name
);
}
};
return (
<>
<SearchAutocomplete
label="Search engineering majors"
defaultItems=
options
onSubmit=
onSubmit
>
(item) => (
<Item>
itemname
</Item>
)
</SearchAutocomplete>
<p>
Results for:' '
major
</p>
</>
);
}
Custom filtering#
By default, useComboBoxState
uses the filter function passed to the defaultFilter
prop (in the above example, a
"contains" function from useFilter
). The filter function can be overridden by users of the SearchAutocomplete
component by
using the items
prop to control the filtered list. When items
is provided rather than defaultItems
, useComboBoxState
does no filtering of its own.
The following example makes the inputValue
controlled, and updates the filtered list that is passed to the items
prop when the input changes value.
function Example() {
let options = [
{id: 1 email: 'fake@email.com'}
{id: 2 email: 'anotherfake@email.com'}
{id: 3 email: 'bob@email.com'}
{id: 4 email: 'joe@email.com'}
{id: 5 email: 'yourEmail@email.com'}
{id: 6 email: 'valid@email.com'}
{id: 7 email: 'spam@email.com'}
{id: 8 email: 'newsletter@email.com'}
{id: 9 email: 'subscribe@email.com'}
];
let {startsWith} = useFilter({sensitivity: 'base'});
let [filterValue setFilterValue] = ReactuseState('');
let filteredItems = ReactuseMemo(
() => optionsfilter((item) => startsWith(itememail filterValue))
[options filterValue]
);
return (
<SearchAutocomplete
label="To:"
items= filteredItems
inputValue= filterValue
onInputChange= setFilterValue
placeholder="Enter recipient email">
(item) => <Item> itememail</Item>
</SearchAutocomplete>
);
}
function Example() {
let options = [
{id: 1 email: 'fake@email.com'}
{id: 2 email: 'anotherfake@email.com'}
{id: 3 email: 'bob@email.com'}
{id: 4 email: 'joe@email.com'}
{id: 5 email: 'yourEmail@email.com'}
{id: 6 email: 'valid@email.com'}
{id: 7 email: 'spam@email.com'}
{id: 8 email: 'newsletter@email.com'}
{id: 9 email: 'subscribe@email.com'}
];
let {startsWith} = useFilter({sensitivity: 'base'});
let [filterValue setFilterValue] = ReactuseState('');
let filteredItems = ReactuseMemo(
() =>
optionsfilter((item) =>
startsWith(itememail filterValue)
)
[options filterValue]
);
return (
<SearchAutocomplete
label="To:"
items= filteredItems
inputValue= filterValue
onInputChange= setFilterValue
placeholder="Enter recipient email">
(item) => <Item> itememail</Item>
</SearchAutocomplete>
);
}
function Example() {
let options = [
{
id: 1
email:
'fake@email.com'
}
{
id: 2
email:
'anotherfake@email.com'
}
{
id: 3
email:
'bob@email.com'
}
{
id: 4
email:
'joe@email.com'
}
{
id: 5
email:
'yourEmail@email.com'
}
{
id: 6
email:
'valid@email.com'
}
{
id: 7
email:
'spam@email.com'
}
{
id: 8
email:
'newsletter@email.com'
}
{
id: 9
email:
'subscribe@email.com'
}
];
let {
startsWith
} = useFilter({
sensitivity: 'base'
});
let [
filterValue
setFilterValue
] = ReactuseState('');
let filteredItems = ReactuseMemo(
() =>
optionsfilter(
(item) =>
startsWith(
itememail
filterValue
)
)
[
options
filterValue
]
);
return (
<SearchAutocomplete
label="To:"
items=
filteredItems
inputValue=
filterValue
onInputChange=
setFilterValue
placeholder="Enter recipient email">
(item) => (
<Item>
itememail
</Item>
)
</SearchAutocomplete>
);
}
Fully controlled#
The following example shows how you would create a controlled SearchAutocomplete, by controlling the input value (inputValue
)
and the autocomplete options (items
). By passing in inputValue
and items
to the SearchAutocomplete
you can control
exactly what your SearchAutocomplete should display. For example, note that the item filtering for the controlled SearchAutocomplete below now follows a "starts with"
filter strategy, accomplished by controlling the exact set of items available to the SearchAutocomplete whenever the input value updates.
function ControlledSearchAutocomplete() {
let optionList = [
{name: 'Red Panda' id: '1'}
{name: 'Cat' id: '2'}
{name: 'Dog' id: '3'}
{name: 'Aardvark' id: '4'}
{name: 'Kangaroo' id: '5'}
{name: 'Snake' id: '6'}
];
// Store SearchAutocomplete input value, selected option, open state, and items
// in a state tracker
let [fieldState setFieldState] = ReactuseState({
inputValue: ''
items: optionList
});
// Implement custom filtering logic and control what items are
// available to the SearchAutocomplete.
let {startsWith} = useFilter({sensitivity: 'base'});
// Specify how each of the SearchAutocomplete values should change when an
// option is selected from the list box
let onSubmit = (value key) => {
setFieldState((prevState) => {
let selectedItem = prevStateitemsfind((option) => optionid === key);
return {
inputValue: selectedItem?name ?? ''
items: optionListfilter((item) =>
startsWith(itemname selectedItem?name ?? '')
)
};
});
};
// Specify how each of the SearchAutocomplete values should change when the input
// field is altered by the user
let onInputChange = (value) => {
setFieldState((prevState) => ({
inputValue: value
items: optionListfilter((item) => startsWith(itemname value))
}));
};
// Show entire list if user opens the menu manually
let onOpenChange = (isOpen menuTrigger) => {
if (menuTrigger === 'manual' && isOpen) {
setFieldState((prevState) => ({
inputValue: prevStateinputValue
items: optionList
}));
}
};
// Pass each controlled prop to useSearchAutocomplete along with their
// change handlers
return (
<SearchAutocomplete
label="Search Animals"
items= fieldStateitems
inputValue= fieldStateinputValue
onOpenChange= onOpenChange
onSubmit= onSubmit
onInputChange= onInputChange>
(item) => <Item> itemname</Item>
</SearchAutocomplete>
);
}
<ControlledSearchAutocomplete />
function ControlledSearchAutocomplete() {
let optionList = [
{name: 'Red Panda' id: '1'}
{name: 'Cat' id: '2'}
{name: 'Dog' id: '3'}
{name: 'Aardvark' id: '4'}
{name: 'Kangaroo' id: '5'}
{name: 'Snake' id: '6'}
];
// Store SearchAutocomplete input value, selected option, open state, and items
// in a state tracker
let [fieldState setFieldState] = ReactuseState({
inputValue: ''
items: optionList
});
// Implement custom filtering logic and control what items are
// available to the SearchAutocomplete.
let {startsWith} = useFilter({sensitivity: 'base'});
// Specify how each of the SearchAutocomplete values should change when an
// option is selected from the list box
let onSubmit = (value key) => {
setFieldState((prevState) => {
let selectedItem = prevStateitemsfind(
(option) => optionid === key
);
return {
inputValue: selectedItem?name ?? ''
items: optionListfilter((item) =>
startsWith(itemname selectedItem?name ?? '')
)
};
});
};
// Specify how each of the SearchAutocomplete values should change when the input
// field is altered by the user
let onInputChange = (value) => {
setFieldState((prevState) => ({
inputValue: value
items: optionListfilter((item) =>
startsWith(itemname value)
)
}));
};
// Show entire list if user opens the menu manually
let onOpenChange = (isOpen menuTrigger) => {
if (menuTrigger === 'manual' && isOpen) {
setFieldState((prevState) => ({
inputValue: prevStateinputValue
items: optionList
}));
}
};
// Pass each controlled prop to useSearchAutocomplete along with their
// change handlers
return (
<SearchAutocomplete
label="Search Animals"
items= fieldStateitems
inputValue= fieldStateinputValue
onOpenChange= onOpenChange
onSubmit= onSubmit
onInputChange= onInputChange>
(item) => <Item> itemname</Item>
</SearchAutocomplete>
);
}
<ControlledSearchAutocomplete />
function ControlledSearchAutocomplete() {
let optionList = [
{
name: 'Red Panda'
id: '1'
}
{
name: 'Cat'
id: '2'
}
{
name: 'Dog'
id: '3'
}
{
name: 'Aardvark'
id: '4'
}
{
name: 'Kangaroo'
id: '5'
}
{
name: 'Snake'
id: '6'
}
];
// Store SearchAutocomplete input value, selected option, open state, and items
// in a state tracker
let [
fieldState
setFieldState
] = ReactuseState({
inputValue: ''
items: optionList
});
// Implement custom filtering logic and control what items are
// available to the SearchAutocomplete.
let {
startsWith
} = useFilter({
sensitivity: 'base'
});
// Specify how each of the SearchAutocomplete values should change when an
// option is selected from the list box
let onSubmit = (
value
key
) => {
setFieldState(
(prevState) => {
let selectedItem = prevStateitemsfind(
(option) =>
optionid ===
key
);
return {
inputValue:
selectedItem?name ??
''
items: optionListfilter(
(item) =>
startsWith(
itemname
selectedItem?name ??
''
)
)
};
}
);
};
// Specify how each of the SearchAutocomplete values should change when the input
// field is altered by the user
let onInputChange = (
value
) => {
setFieldState(
(prevState) => ({
inputValue: value
items: optionListfilter(
(item) =>
startsWith(
itemname
value
)
)
})
);
};
// Show entire list if user opens the menu manually
let onOpenChange = (
isOpen
menuTrigger
) => {
if (
menuTrigger ===
'manual' &&
isOpen
) {
setFieldState(
(prevState) => ({
inputValue:
prevStateinputValue
items: optionList
})
);
}
};
// Pass each controlled prop to useSearchAutocomplete along with their
// change handlers
return (
<SearchAutocomplete
label="Search Animals"
items=
fieldStateitems
inputValue=
fieldStateinputValue
onOpenChange=
onOpenChange
onSubmit= onSubmit
onInputChange=
onInputChange
>
(item) => (
<Item>
itemname
</Item>
)
</SearchAutocomplete>
);
}
<ControlledSearchAutocomplete />
Menu trigger behavior#
useComboBoxState
supports three different menuTrigger
prop values:
input
(default): SearchAutocomplete menu opens when the user edits the input text.focus
: SearchAutocomplete menu opens when the user focuses the SearchAutocomplete input.manual
: SearchAutocomplete menu only opens when the user presses the trigger button or uses the arrow keys.
The example below has menuTrigger
set to focus
.
<SearchAutocomplete label="Search Animals" menuTrigger="focus">
<Item key="red panda">Red Panda</Item>
<Item key="cat">Cat</Item>
<Item key="dog">Dog</Item>
<Item key="aardvark">Aardvark</Item>
<Item key="kangaroo">Kangaroo</Item>
<Item key="snake">Snake</Item>
</SearchAutocomplete>
<SearchAutocomplete
label="Search Animals"
menuTrigger="focus">
<Item key="red panda">Red Panda</Item>
<Item key="cat">Cat</Item>
<Item key="dog">Dog</Item>
<Item key="aardvark">Aardvark</Item>
<Item key="kangaroo">Kangaroo</Item>
<Item key="snake">Snake</Item>
</SearchAutocomplete>
<SearchAutocomplete
label="Search Animals"
menuTrigger="focus">
<Item key="red panda">
Red Panda
</Item>
<Item key="cat">
Cat
</Item>
<Item key="dog">
Dog
</Item>
<Item key="aardvark">
Aardvark
</Item>
<Item key="kangaroo">
Kangaroo
</Item>
<Item key="snake">
Snake
</Item>
</SearchAutocomplete>
Disabled options#
You can disable specific options by providing an array of keys to useComboBoxState
via the disabledKeys
prop. This will prevent options with matching keys from being pressable and
receiving keyboard focus as shown in the example below. Note that you are responsible for the styling of disabled options.
<SearchAutocomplete label="Search Animals" disabledKeys=['cat' 'kangaroo']>
<Item key="red panda">Red Panda</Item>
<Item key="cat">Cat</Item>
<Item key="dog">Dog</Item>
<Item key="aardvark">Aardvark</Item>
<Item key="kangaroo">Kangaroo</Item>
<Item key="snake">Snake</Item>
</SearchAutocomplete>
<SearchAutocomplete
label="Search Animals"
disabledKeys=['cat' 'kangaroo']>
<Item key="red panda">Red Panda</Item>
<Item key="cat">Cat</Item>
<Item key="dog">Dog</Item>
<Item key="aardvark">Aardvark</Item>
<Item key="kangaroo">Kangaroo</Item>
<Item key="snake">Snake</Item>
</SearchAutocomplete>
<SearchAutocomplete
label="Search Animals"
disabledKeys=[
'cat'
'kangaroo'
]>
<Item key="red panda">
Red Panda
</Item>
<Item key="cat">
Cat
</Item>
<Item key="dog">
Dog
</Item>
<Item key="aardvark">
Aardvark
</Item>
<Item key="kangaroo">
Kangaroo
</Item>
<Item key="snake">
Snake
</Item>
</SearchAutocomplete>
Asynchronous loading#
This example uses the useAsyncList hook to handle asynchronous loading and filtering of data from a server. You may additionally want to display a spinner to indicate the loading state to the user, or support features like infinite scroll to load more data.
import {useAsyncList} from '@react-stately/data';
function AsyncLoadingExample() {
let list = useAsyncList({
async load({signal filterText}) {
let res = await fetch(
`https://swapi.dev/api/people/?search= `
{signal}
);
let json = await resjson();
return {
items: jsonresults
};
}
});
return (
<SearchAutocomplete
label="Search Star Wars Characters"
items= listitems
inputValue= listfilterText
onInputChange= listsetFilterText>
(item) => <Item key= itemname> itemname</Item>
</SearchAutocomplete>
);
}
import {useAsyncList} from '@react-stately/data';
function AsyncLoadingExample() {
let list = useAsyncList({
async load({signal filterText}) {
let res = await fetch(
`https://swapi.dev/api/people/?search= `
{signal}
);
let json = await resjson();
return {
items: jsonresults
};
}
});
return (
<SearchAutocomplete
label="Search Star Wars Characters"
items= listitems
inputValue= listfilterText
onInputChange= listsetFilterText>
(item) => <Item key= itemname> itemname</Item>
</SearchAutocomplete>
);
}
import {useAsyncList} from '@react-stately/data';
function AsyncLoadingExample() {
let list = useAsyncList(
{
async load({
signal
filterText
}) {
let res = await fetch(
`https://swapi.dev/api/people/?search= `
{signal}
);
let json = await resjson();
return {
items:
jsonresults
};
}
}
);
return (
<SearchAutocomplete
label="Search Star Wars Characters"
items= listitems
inputValue=
listfilterText
onInputChange=
listsetFilterText
>
(item) => (
<Item
key=
itemname
>
itemname
</Item>
)
</SearchAutocomplete>
);
}
Internationalization#
useSearchAutocomplete
handles some aspects of internationalization automatically.
For example, the item focus, count, and selection VoiceOver announcements are localized.
You are responsible for localizing all labels and option
content that is passed into the autocomplete.