useComboBox
Provides the behavior and accessibility implementation for a ComboBox component. A ComboBox combines a text entry with a picker menu, allowing users to filter longer lists to only the selections matching a query.
| install | yarn add @react-aria/combobox |
|---|---|
| version | 3.0.0-alpha.0 |
| usage | import {useComboBox} from '@react-aria/combobox' |
API#
useComboBox<T>(
(props: AriaComboBoxProps<T>,
, state: ComboBoxState<T>
)): ComboBoxAriaFeatures#
There is no native element to implement a ComboBox in HTML. useComboBox helps achieve accessible ComboBox components that can
be styled as needed.
- Exposed to assistive technology as a ComboBox
- Support for selecting a single option
- Support for disabled options
- Support for sections
- 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 ComboBox menu using the arrow keys, including automatically focusing the first or last item accordingly
- Virtual focus management for ComboBox menu option navigation
- VoiceOver announcement enhancements for option focusing, filtering, and selection
Anatomy#
A ComboBox consists of a label, an input which displays the current value, a listbox popup, and a button
used to toggle the listbox popup open state. Users can type within the input to filter the available options
within the listbox popup. The listbox popup may be opened by a variety of input field interactions specified
by the menuTrigger prop provided to useComboBox, or by clicking or touching the ComboBox trigger button. useComboBox handles exposing
the correct ARIA attributes for accessibility for each of the components comprising the ComboBox. It should be combined
with useListBox, which handles the implementation of the popup listbox,
and useButton which handles the button press handlers returned by useComboBox.
useComboBox returns props that you should spread onto the appropriate elements:
| Name | Type | Description |
buttonProps | AriaButtonProps | Props for the ComboBox menu trigger button. |
inputProps | InputHTMLAttributes<HTMLInputElement> | Props for the ComboBox input element. |
listBoxProps | HTMLAttributes<HTMLElement> | Props for the ComboBox menu. |
labelProps | HTMLAttributes<HTMLElement> | Props for the ComboBox label element. |
State is managed by the useComboBoxState hook from @react-stately/select.
The state object should be passed as an option to useComboBox.
If the ComboBox does not have a visible label, an aria-label or aria-labelledby prop must be passed instead to
identify it to assistive technology.
State management#
useComboBox requires knowledge of the options in the ComboBox 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 ComboBox is focused, and the current input value.
For more information about selection, see Selection.
Usage#
Uncontrolled#
This base example uses an <input> element for the ComboBox text input and a <button> element for the ComboBox menu trigger. A <span>
is included within the <button> to display the dropdown arrow icon (hidden from screen readers with aria-hidden).
A "contains" filter function is obtained from useFilter and is passed to useComboBoxState so
that the ComboBox list can be filtered based on the option text and the current ComboBox input text. The input value, selected option, and menu open state is completely
uncontrolled, with the default selected option set by the defaultSelectedKey prop and handled by useComboBoxState.
The listbox popup uses useListBox
and useOption to render the
list of options. A hidden <DismissButton>
is added at the start and end of the popup to allow screen reader users to dismiss the popup. Note that shouldUseVirtualFocus is passed to useListBox
and useOption so that browser focus remains within the ComboBox <input> element even when interacting with the ComboBox menu options.
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 {mergeProps} from '@react-aria/utils';
import {useButton} from '@react-aria/button';
import {useComboBoxState} from '@react-stately/combobox';
import {useFilter} from '@react-aria/i18n';
import {useListBox useOption} from '@react-aria/listbox';
import {useOverlay DismissButton} from '@react-aria/overlays';
function ComboBox(props) {
// Get a basic "contains" filter function for input value
// and option text comparison
let {contains} = useFilter({sensitivity: 'base'});
// Create state based on the incoming props and the filter function
let state = useComboBoxState({...props defaultFilter: contains});
// Get props for child elements from useComboBox
let triggerRef = ReactuseRef();
let inputRef = ReactuseRef();
let listBoxRef = ReactuseRef();
let popoverRef = ReactuseRef();
let {
buttonProps: triggerProps
inputProps
listBoxProps
labelProps
} = useComboBox(
{
...props
inputRef
buttonRef: triggerRef
listBoxRef
popoverRef
menuTrigger: 'input'
}
state
);
// Get props for the ComboBox trigger button based on the button
// props from useComboBox
let {buttonProps} = useButton(triggerProps triggerRef);
return (
<div style={position: 'relative' display: 'inline-block'}>
<div ...labelProps>propslabel</div>
<input
...inputProps
ref=inputRef
style={
borderRight: 0
borderBottomRightRadius: 0
borderTopRightRadius: 0
}
/>
<button
...buttonProps
ref=triggerRef
style={
height: '21px'
borderBottomLeftRadius: 0
borderTopLeftRadius: 0
}>
<span aria-hidden="true" style={paddingLeft: 5}>
▼
</span>
</button>
stateisOpen && (
<ListBoxPopup
...listBoxProps
// Use virtual focus to get aria-activedescendant tracking and
// ensure focus doesn't leave the input field
shouldUseVirtualFocus
listBoxRef=listBoxRef
popoverRef=popoverRef
state=state
/>
)
</div>
);
}
function ListBoxPopup(props) {
let {
popoverRef
listBoxRef
state
shouldUseVirtualFocus
...otherProps
} = props;
// Get props for the listbox.
// Prevent focus moving to listbox via shouldUseVirtualFocus
let {listBoxProps} = useListBox(
{
autoFocus: statefocusStrategy
disallowEmptySelection: true
shouldUseVirtualFocus
...otherProps
}
state
listBoxRef
);
// Handle events that should cause the popup to close,
// e.g. blur, clicking outside, or pressing the escape key.
let {overlayProps} = useOverlay(
{
onClose: () => stateclose()
shouldCloseOnBlur: true
isOpen: stateisOpen
isDismissable: true
}
popoverRef
);
// Add hidden <DismissButton> components at the start and end of the list
// to allow screen reader users to dismiss the popup easily.
return (
<div ...overlayProps ref=popoverRef>
<DismissButton onDismiss=() => stateclose() />
<ul
...mergeProps(listBoxProps otherProps)
ref=listBoxRef
style={
position: 'absolute'
width: '100%'
margin: '4px 0 0 0'
padding: 0
listStyle: 'none'
border: '1px solid gray'
background: 'lightgray'
}>
[...statecollection]map((item) => (
<Option
shouldUseVirtualFocus
key=itemkey
item=item
state=state
/>
))
</ul>
<DismissButton onDismiss=() => stateclose() />
</div>
);
}
function Option({item state shouldUseVirtualFocus}) {
let ref = ReactuseRef();
let isDisabled = statedisabledKeyshas(itemkey);
let isSelected = stateselectionManagerisSelected(itemkey);
// Track focus via focusedKey state instead of with focus event listeners
// since focus never leaves the text input in a ComboBox
let isFocused = stateselectionManagerfocusedKey === itemkey;
// Get props for the option element.
// Prevent options from receiving browser focus via shouldUseVirtualFocus.
let {optionProps} = useOption(
{
key: itemkey
isDisabled
isSelected
shouldSelectOnPressUp: true
shouldFocusOnHover: true
shouldUseVirtualFocus
}
state
ref
);
let backgroundColor;
let color;
if (isSelected) {
backgroundColor = 'blueviolet';
color = 'white';
} else if (isFocused) {
backgroundColor = 'gray';
color = 'black';
} 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>
);
}
<ComboBox label="Favorite Animal" defaultSelectedKey="red panda">
<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>
</ComboBox>import {Item} from '@react-stately/collections';
import {mergeProps} from '@react-aria/utils';
import {useButton} from '@react-aria/button';
import {useComboBoxState} from '@react-stately/combobox';
import {useFilter} from '@react-aria/i18n';
import {useListBox useOption} from '@react-aria/listbox';
import {
useOverlay
DismissButton
} from '@react-aria/overlays';
function ComboBox(props) {
// Get a basic "contains" filter function for input value
// and option text comparison
let {contains} = useFilter({sensitivity: 'base'});
// Create state based on the incoming props and the filter function
let state = useComboBoxState({
...props
defaultFilter: contains
});
// Get props for child elements from useComboBox
let triggerRef = ReactuseRef();
let inputRef = ReactuseRef();
let listBoxRef = ReactuseRef();
let popoverRef = ReactuseRef();
let {
buttonProps: triggerProps
inputProps
listBoxProps
labelProps
} = useComboBox(
{
...props
inputRef
buttonRef: triggerRef
listBoxRef
popoverRef
menuTrigger: 'input'
}
state
);
// Get props for the ComboBox trigger button based on the button
// props from useComboBox
let {buttonProps} = useButton(triggerProps triggerRef);
return (
<div
style={
position: 'relative'
display: 'inline-block'
}>
<div ...labelProps>propslabel</div>
<input
...inputProps
ref=inputRef
style={
borderRight: 0
borderBottomRightRadius: 0
borderTopRightRadius: 0
}
/>
<button
...buttonProps
ref=triggerRef
style={
height: '21px'
borderBottomLeftRadius: 0
borderTopLeftRadius: 0
}>
<span aria-hidden="true" style={paddingLeft: 5}>
▼
</span>
</button>
stateisOpen && (
<ListBoxPopup
...listBoxProps
// Use virtual focus to get aria-activedescendant tracking and
// ensure focus doesn't leave the input field
shouldUseVirtualFocus
listBoxRef=listBoxRef
popoverRef=popoverRef
state=state
/>
)
</div>
);
}
function ListBoxPopup(props) {
let {
popoverRef
listBoxRef
state
shouldUseVirtualFocus
...otherProps
} = props;
// Get props for the listbox.
// Prevent focus moving to listbox via shouldUseVirtualFocus
let {listBoxProps} = useListBox(
{
autoFocus: statefocusStrategy
disallowEmptySelection: true
shouldUseVirtualFocus
...otherProps
}
state
listBoxRef
);
// Handle events that should cause the popup to close,
// e.g. blur, clicking outside, or pressing the escape key.
let {overlayProps} = useOverlay(
{
onClose: () => stateclose()
shouldCloseOnBlur: true
isOpen: stateisOpen
isDismissable: true
}
popoverRef
);
// Add hidden <DismissButton> components at the start and end of the list
// to allow screen reader users to dismiss the popup easily.
return (
<div ...overlayProps ref=popoverRef>
<DismissButton onDismiss=() => stateclose() />
<ul
...mergeProps(listBoxProps otherProps)
ref=listBoxRef
style={
position: 'absolute'
width: '100%'
margin: '4px 0 0 0'
padding: 0
listStyle: 'none'
border: '1px solid gray'
background: 'lightgray'
}>
[...statecollection]map((item) => (
<Option
shouldUseVirtualFocus
key=itemkey
item=item
state=state
/>
))
</ul>
<DismissButton onDismiss=() => stateclose() />
</div>
);
}
function Option({item state shouldUseVirtualFocus}) {
let ref = ReactuseRef();
let isDisabled = statedisabledKeyshas(itemkey);
let isSelected = stateselectionManagerisSelected(
itemkey
);
// Track focus via focusedKey state instead of with focus event listeners
// since focus never leaves the text input in a ComboBox
let isFocused =
stateselectionManagerfocusedKey === itemkey;
// Get props for the option element.
// Prevent options from receiving browser focus via shouldUseVirtualFocus.
let {optionProps} = useOption(
{
key: itemkey
isDisabled
isSelected
shouldSelectOnPressUp: true
shouldFocusOnHover: true
shouldUseVirtualFocus
}
state
ref
);
let backgroundColor;
let color;
if (isSelected) {
backgroundColor = 'blueviolet';
color = 'white';
} else if (isFocused) {
backgroundColor = 'gray';
color = 'black';
} 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>
);
}
<ComboBox
label="Favorite Animal"
defaultSelectedKey="red panda">
<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>
</ComboBox>import {Item} from '@react-stately/collections';
import {mergeProps} from '@react-aria/utils';
import {useButton} from '@react-aria/button';
import {useComboBoxState} from '@react-stately/combobox';
import {useFilter} from '@react-aria/i18n';
import {
useListBox
useOption
} from '@react-aria/listbox';
import {
useOverlay
DismissButton
} from '@react-aria/overlays';
function ComboBox(
props
) {
// Get a basic "contains" filter function for input value
// and option text comparison
let {
contains
} = useFilter({
sensitivity: 'base'
});
// Create state based on the incoming props and the filter function
let state = useComboBoxState(
{
...props
defaultFilter: contains
}
);
// Get props for child elements from useComboBox
let triggerRef = ReactuseRef();
let inputRef = ReactuseRef();
let listBoxRef = ReactuseRef();
let popoverRef = ReactuseRef();
let {
buttonProps: triggerProps
inputProps
listBoxProps
labelProps
} = useComboBox(
{
...props
inputRef
buttonRef: triggerRef
listBoxRef
popoverRef
menuTrigger:
'input'
}
state
);
// Get props for the ComboBox trigger button based on the button
// props from useComboBox
let {
buttonProps
} = useButton(
triggerProps
triggerRef
);
return (
<div
style={
position:
'relative'
display:
'inline-block'
}>
<div
...labelProps>
propslabel
</div>
<input
...inputProps
ref=inputRef
style={
borderRight: 0
borderBottomRightRadius: 0
borderTopRightRadius: 0
}
/>
<button
...buttonProps
ref=triggerRef
style={
height: '21px'
borderBottomLeftRadius: 0
borderTopLeftRadius: 0
}>
<span
aria-hidden="true"
style={
paddingLeft: 5
}>
▼
</span>
</button>
stateisOpen && (
<ListBoxPopup
...listBoxProps
// Use virtual focus to get aria-activedescendant tracking and
// ensure focus doesn't leave the input field
shouldUseVirtualFocus
listBoxRef=
listBoxRef
popoverRef=
popoverRef
state=state
/>
)
</div>
);
}
function ListBoxPopup(
props
) {
let {
popoverRef
listBoxRef
state
shouldUseVirtualFocus
...otherProps
} = props;
// Get props for the listbox.
// Prevent focus moving to listbox via shouldUseVirtualFocus
let {
listBoxProps
} = useListBox(
{
autoFocus:
statefocusStrategy
disallowEmptySelection: true
shouldUseVirtualFocus
...otherProps
}
state
listBoxRef
);
// Handle events that should cause the popup to close,
// e.g. blur, clicking outside, or pressing the escape key.
let {
overlayProps
} = useOverlay(
{
onClose: () =>
stateclose()
shouldCloseOnBlur: true
isOpen:
stateisOpen
isDismissable: true
}
popoverRef
);
// Add hidden <DismissButton> components at the start and end of the list
// to allow screen reader users to dismiss the popup easily.
return (
<div
...overlayProps
ref=popoverRef>
<DismissButton
onDismiss=() =>
stateclose()
/>
<ul
...mergeProps(
listBoxProps
otherProps
)
ref=listBoxRef
style={
position:
'absolute'
width: '100%'
margin:
'4px 0 0 0'
padding: 0
listStyle:
'none'
border:
'1px solid gray'
background:
'lightgray'
}>
[
...statecollection
]map((item) => (
<Option
shouldUseVirtualFocus
key=
itemkey
item=item
state=state
/>
))
</ul>
<DismissButton
onDismiss=() =>
stateclose()
/>
</div>
);
}
function Option({
item
state
shouldUseVirtualFocus
}) {
let ref = ReactuseRef();
let isDisabled = statedisabledKeyshas(
itemkey
);
let isSelected = stateselectionManagerisSelected(
itemkey
);
// Track focus via focusedKey state instead of with focus event listeners
// since focus never leaves the text input in a ComboBox
let isFocused =
state
selectionManager
focusedKey ===
itemkey;
// Get props for the option element.
// Prevent options from receiving browser focus via shouldUseVirtualFocus.
let {
optionProps
} = useOption(
{
key: itemkey
isDisabled
isSelected
shouldSelectOnPressUp: true
shouldFocusOnHover: true
shouldUseVirtualFocus
}
state
ref
);
let backgroundColor;
let color;
if (isSelected) {
backgroundColor =
'blueviolet';
color = 'white';
} else if (isFocused) {
backgroundColor =
'gray';
color = 'black';
} 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>
);
}
<ComboBox
label="Favorite Animal"
defaultSelectedKey="red panda">
<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>
</ComboBox>Controlled#
The following example shows how you would create a controlled ComboBox, controlling everything from the selected value (selectedKey)
to the combobox options (items). By passing in inputValue, selectedKey, isOpen, and items to the useComboBoxState hook you can control
what exactly your ComboBox should display.
It is important to note that you don't have to control every single aspect of a ComboBox. If you decide to only control a single property of the
ComboBox, be sure to provide the change handler for that prop as well e.g. controlling selectedKey would require onSelectionChange to be passed to useComboBox as well.
// Using the same ComboBox component code from the previous example...
function ControlledComboBox() {
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 ComboBox input value, selected option, and open state
// in a state tracker
let [fieldState setFieldState] = ReactuseState({
isOpen: false
selectedKey: ''
inputValue: ''
});
// Implement custom filtering logic and control what items are
// available to the ComboBox.
// Memoize filteredItems so it updates when the inputValue updates.
let {startsWith} = useFilter({sensitivity: 'base'});
let filteredItems = ReactuseMemo(
() =>
optionListfilter((item) => startsWith(itemname fieldStateinputValue))
[optionList fieldStateinputValue]
);
// Specify how each of the ComboBox values should change when an
// option is selected from the menu
let onSelectionChange = (key) => {
setFieldState({
isOpen: false
inputValue: filteredItemsfind((option) => optionid === key)?name ?? ''
selectedKey: key
});
};
// Specify how each of the ComboBox values should change when the input
// field is altered by the end user
let onInputChange = (value) => {
setFieldState((prevState) => ({
isOpen: true
inputValue: value
selectedKey: value === '' ? null : prevStateselectedKey
}));
};
// Specify how each of the ComboBox values should change when the menu's
// open state is changed
let onOpenChange = (isOpen) => {
setFieldState((prevState) => ({
isOpen
inputValue: prevStateinputValue
selectedKey: prevStateselectedKey
}));
};
// Pass each controlled prop to useComboBox along with their
// change handlers
return (
<ComboBox
label="Favorite Animal"
items=filteredItems
selectedKey=fieldStateselectedKey
inputValue=fieldStateinputValue
isOpen=fieldStateisOpen
onOpenChange=onOpenChange
onSelectionChange=onSelectionChange
onInputChange=onInputChange>
(item) => <Item>itemname</Item>
</ComboBox>
);
}
<ControlledComboBox />// Using the same ComboBox component code from the previous example...
function ControlledComboBox() {
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 ComboBox input value, selected option, and open state
// in a state tracker
let [fieldState setFieldState] = ReactuseState({
isOpen: false
selectedKey: ''
inputValue: ''
});
// Implement custom filtering logic and control what items are
// available to the ComboBox.
// Memoize filteredItems so it updates when the inputValue updates.
let {startsWith} = useFilter({sensitivity: 'base'});
let filteredItems = ReactuseMemo(
() =>
optionListfilter((item) =>
startsWith(itemname fieldStateinputValue)
)
[optionList fieldStateinputValue]
);
// Specify how each of the ComboBox values should change when an
// option is selected from the menu
let onSelectionChange = (key) => {
setFieldState({
isOpen: false
inputValue:
filteredItemsfind((option) => optionid === key)
?name ?? ''
selectedKey: key
});
};
// Specify how each of the ComboBox values should change when the input
// field is altered by the end user
let onInputChange = (value) => {
setFieldState((prevState) => ({
isOpen: true
inputValue: value
selectedKey:
value === '' ? null : prevStateselectedKey
}));
};
// Specify how each of the ComboBox values should change when the menu's
// open state is changed
let onOpenChange = (isOpen) => {
setFieldState((prevState) => ({
isOpen
inputValue: prevStateinputValue
selectedKey: prevStateselectedKey
}));
};
// Pass each controlled prop to useComboBox along with their
// change handlers
return (
<ComboBox
label="Favorite Animal"
items=filteredItems
selectedKey=fieldStateselectedKey
inputValue=fieldStateinputValue
isOpen=fieldStateisOpen
onOpenChange=onOpenChange
onSelectionChange=onSelectionChange
onInputChange=onInputChange>
(item) => <Item>itemname</Item>
</ComboBox>
);
}
<ControlledComboBox />// Using the same ComboBox component code from the previous example...
function ControlledComboBox() {
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 ComboBox input value, selected option, and open state
// in a state tracker
let [
fieldState
setFieldState
] = ReactuseState({
isOpen: false
selectedKey: ''
inputValue: ''
});
// Implement custom filtering logic and control what items are
// available to the ComboBox.
// Memoize filteredItems so it updates when the inputValue updates.
let {
startsWith
} = useFilter({
sensitivity: 'base'
});
let filteredItems = ReactuseMemo(
() =>
optionListfilter(
(item) =>
startsWith(
itemname
fieldStateinputValue
)
)
[
optionList
fieldStateinputValue
]
);
// Specify how each of the ComboBox values should change when an
// option is selected from the menu
let onSelectionChange = (
key
) => {
setFieldState({
isOpen: false
inputValue:
filteredItemsfind(
(option) =>
optionid ===
key
)?name ?? ''
selectedKey: key
});
};
// Specify how each of the ComboBox values should change when the input
// field is altered by the end user
let onInputChange = (
value
) => {
setFieldState(
(prevState) => ({
isOpen: true
inputValue: value
selectedKey:
value === ''
? null
: prevStateselectedKey
})
);
};
// Specify how each of the ComboBox values should change when the menu's
// open state is changed
let onOpenChange = (
isOpen
) => {
setFieldState(
(prevState) => ({
isOpen
inputValue:
prevStateinputValue
selectedKey:
prevStateselectedKey
})
);
};
// Pass each controlled prop to useComboBox along with their
// change handlers
return (
<ComboBox
label="Favorite Animal"
items=
filteredItems
selectedKey=
fieldStateselectedKey
inputValue=
fieldStateinputValue
isOpen=
fieldStateisOpen
onOpenChange=
onOpenChange
onSelectionChange=
onSelectionChange
onInputChange=
onInputChange
>
(item) => (
<Item>
itemname
</Item>
)
</ComboBox>
);
}
<ControlledComboBox />Allow custom values#
By default, useComboBoxState doesn't allow users to specify a value that doesn't exist in the list of options and will revert the input value to
the current selected value on blur. By specifying allowsCustomValue, this behavior is suppressed and the user is free to enter
any value within the field.
// Using the same ComboBox component code from the first example...
<ComboBox label="Favorite Animal" allowsCustomValue>
<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>
</ComboBox>// Using the same ComboBox component code from the first example...
<ComboBox label="Favorite Animal" allowsCustomValue>
<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>
</ComboBox>// Using the same ComboBox component code from the first example...
<ComboBox
label="Favorite Animal"
allowsCustomValue>
<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>
</ComboBox>Menu trigger behavior#
useComboBoxState supports three different menuTrigger prop values:
input(default): ComboBox menu opens when the user edits the input text.focus: ComboBox menu opens when the user focuses the ComboBox input.manual: ComboBox menu only opens when the user presses the trigger button.
The example below has menuTrigger set to focus.
// Using the same ComboBox component code from the first example...
<ComboBox label="Favorite Animal" 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>
</ComboBox>// Using the same ComboBox component code from the first example...
<ComboBox label="Favorite Animal" 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>
</ComboBox>// Using the same ComboBox component code from the first example...
<ComboBox
label="Favorite Animal"
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>
</ComboBox>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.
// Using the same ComboBox component code from the first example...
<ComboBox label="Favorite Animal" 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>
</ComboBox>// Using the same ComboBox component code from the first example...
<ComboBox
label="Favorite Animal"
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>
</ComboBox>// Using the same ComboBox component code from the first example...
<ComboBox
label="Favorite Animal"
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>
</ComboBox>Internationalization#
useComboBox handles some aspects of internationalization automatically.
For example, the item focus, count, and selection VoiceOver announcements are formatted to match the current locale.
You are responsible for localizing all labels and option
content that is passed into the select.
RTL#
In right-to-left languages, the ComboBox should be mirrored. The trigger button should be on the left, and the input element should be on the right. In addition, the content of ComboBox options should flip. Ensure that your CSS accounts for this.