ComboBox
A combo box combines a text input with a listbox, allowing users to filter a list of options to items matching a query.
install | yarn add react-aria-components |
---|---|
version | 1.4.2 |
usage | import {ComboBox} from 'react-aria-components' |
Example#
import {Button, ComboBox, Input, Label, ListBox, ListBoxItem, Popover} from 'react-aria-components';
<ComboBox>
<Label>Favorite Animal</Label>
<div>
<Input />
<Button>▼</Button>
</div>
<Popover>
<ListBox>
<ListBoxItem>Aardvark</ListBoxItem>
<ListBoxItem>Cat</ListBoxItem>
<ListBoxItem>Dog</ListBoxItem>
<ListBoxItem>Kangaroo</ListBoxItem>
<ListBoxItem>Panda</ListBoxItem>
<ListBoxItem>Snake</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
import {
Button,
ComboBox,
Input,
Label,
ListBox,
ListBoxItem,
Popover
} from 'react-aria-components';
<ComboBox>
<Label>Favorite Animal</Label>
<div>
<Input />
<Button>▼</Button>
</div>
<Popover>
<ListBox>
<ListBoxItem>Aardvark</ListBoxItem>
<ListBoxItem>Cat</ListBoxItem>
<ListBoxItem>Dog</ListBoxItem>
<ListBoxItem>Kangaroo</ListBoxItem>
<ListBoxItem>Panda</ListBoxItem>
<ListBoxItem>Snake</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
import {
Button,
ComboBox,
Input,
Label,
ListBox,
ListBoxItem,
Popover
} from 'react-aria-components';
<ComboBox>
<Label>
Favorite Animal
</Label>
<div>
<Input />
<Button>▼</Button>
</div>
<Popover>
<ListBox>
<ListBoxItem>
Aardvark
</ListBoxItem>
<ListBoxItem>
Cat
</ListBoxItem>
<ListBoxItem>
Dog
</ListBoxItem>
<ListBoxItem>
Kangaroo
</ListBoxItem>
<ListBoxItem>
Panda
</ListBoxItem>
<ListBoxItem>
Snake
</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
Show CSS
@import "@react-aria/example-theme";
.react-aria-ComboBox {
color: var(--text-color);
.react-aria-Input {
margin: 0;
font-size: 1.072rem;
background: var(--field-background);
color: var(--field-text-color);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 0.286rem 2rem 0.286rem 0.571rem;
vertical-align: middle;
&[data-focused] {
outline: none;
outline: 2px solid var(--focus-ring-color);
outline-offset: -1px;
}
}
.react-aria-Button {
background: var(--highlight-background);
color: var(--highlight-foreground);
forced-color-adjust: none;
border-radius: 4px;
border: none;
margin-left: -1.714rem;
width: 1.429rem;
height: 1.429rem;
padding: 0;
font-size: 0.857rem;
cursor: default;
&[data-pressed] {
box-shadow: none;
background: var(--highlight-background);
}
}
}
.react-aria-Popover[data-trigger=ComboBox] {
width: var(--trigger-width);
.react-aria-ListBox {
display: block;
width: unset;
max-height: inherit;
min-height: unset;
border: none;
.react-aria-Header {
padding-left: 1.571rem;
}
}
.react-aria-ListBoxItem {
padding: 0.286rem 0.571rem 0.286rem 1.571rem;
&[data-focus-visible] {
outline: none;
}
&[data-selected] {
font-weight: 600;
background: unset;
color: var(--text-color);
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&[data-focused],
&[data-pressed] {
background: var(--highlight-background);
color: var(--highlight-foreground);
}
}
}
@import "@react-aria/example-theme";
.react-aria-ComboBox {
color: var(--text-color);
.react-aria-Input {
margin: 0;
font-size: 1.072rem;
background: var(--field-background);
color: var(--field-text-color);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 0.286rem 2rem 0.286rem 0.571rem;
vertical-align: middle;
&[data-focused] {
outline: none;
outline: 2px solid var(--focus-ring-color);
outline-offset: -1px;
}
}
.react-aria-Button {
background: var(--highlight-background);
color: var(--highlight-foreground);
forced-color-adjust: none;
border-radius: 4px;
border: none;
margin-left: -1.714rem;
width: 1.429rem;
height: 1.429rem;
padding: 0;
font-size: 0.857rem;
cursor: default;
&[data-pressed] {
box-shadow: none;
background: var(--highlight-background);
}
}
}
.react-aria-Popover[data-trigger=ComboBox] {
width: var(--trigger-width);
.react-aria-ListBox {
display: block;
width: unset;
max-height: inherit;
min-height: unset;
border: none;
.react-aria-Header {
padding-left: 1.571rem;
}
}
.react-aria-ListBoxItem {
padding: 0.286rem 0.571rem 0.286rem 1.571rem;
&[data-focus-visible] {
outline: none;
}
&[data-selected] {
font-weight: 600;
background: unset;
color: var(--text-color);
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&[data-focused],
&[data-pressed] {
background: var(--highlight-background);
color: var(--highlight-foreground);
}
}
}
@import "@react-aria/example-theme";
.react-aria-ComboBox {
color: var(--text-color);
.react-aria-Input {
margin: 0;
font-size: 1.072rem;
background: var(--field-background);
color: var(--field-text-color);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 0.286rem 2rem 0.286rem 0.571rem;
vertical-align: middle;
&[data-focused] {
outline: none;
outline: 2px solid var(--focus-ring-color);
outline-offset: -1px;
}
}
.react-aria-Button {
background: var(--highlight-background);
color: var(--highlight-foreground);
forced-color-adjust: none;
border-radius: 4px;
border: none;
margin-left: -1.714rem;
width: 1.429rem;
height: 1.429rem;
padding: 0;
font-size: 0.857rem;
cursor: default;
&[data-pressed] {
box-shadow: none;
background: var(--highlight-background);
}
}
}
.react-aria-Popover[data-trigger=ComboBox] {
width: var(--trigger-width);
.react-aria-ListBox {
display: block;
width: unset;
max-height: inherit;
min-height: unset;
border: none;
.react-aria-Header {
padding-left: 1.571rem;
}
}
.react-aria-ListBoxItem {
padding: 0.286rem 0.571rem 0.286rem 1.571rem;
&[data-focus-visible] {
outline: none;
}
&[data-selected] {
font-weight: 600;
background: unset;
color: var(--text-color);
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&[data-focused],
&[data-pressed] {
background: var(--highlight-background);
color: var(--highlight-foreground);
}
}
}
Features#
A combo box can be built using the <datalist> HTML element, but this is very limited in functionality and difficult to style. ComboBox
helps achieve accessible combo box and autocomplete components that can be styled as needed.
- Flexible – Support for selecting pre-defined values, custom values, controlled and uncontrolled state, custom filter functions, async loading, disabled items, validation, and multiple menu trigger options.
- Keyboard navigation – ComboBox can be opened and navigated using the arrow keys, along with page up/down, home/end, etc. The list of options is filtered while typing into the input, and items can be selected with the enter key.
- Accessible – Follows the ARIA combobox pattern, with support for items and sections, and slots for label and description elements within each item. Custom localized announcements are included for option focusing, filtering, and selection using an ARIA live region to ensure announcements are clear and consistent.
- Validation – Support for native HTML constraint validation with customizable UI, custom validation functions, realtime validation, and server-side validation errors.
- Styleable – Items include builtin states for styling, such as hover, press, focus, selected, and disabled.
Read our blog post for more details about the interactions and accessibility features implemented by ComboBox
.
Anatomy#
A combo box consists of a label, an input which displays the current value, a listbox displayed in a popover, and an optional button
used to toggle the popover open state. Users can type within the input to filter the available options
within the list box. The list box popover may be opened by a variety of input field interactions specified
by the menuTrigger
prop provided to ComboBox
, or by clicking or touching the button.
ComboBox
also supports optional description and error message elements, which can be used
to provide more context about the field, and any validation messages. These are linked with the
input via the aria-describedby
attribute.
import {Button, ComboBox, FieldError, Header, Input, Label, ListBox, ListBoxItem, ListBoxSection, Popover, Text} from 'react-aria-components';
<ComboBox>
<Label />
<Input />
<Button />
<Text slot="description" />
<FieldError />
<Popover>
<ListBox>
<ListBoxItem>
<Text slot="label" />
<Text slot="description" />
</ListBoxItem>
<ListBoxSection>
<Header />
<ListBoxItem />
</ListBoxSection>
</ListBox>
</Popover>
</ComboBox>
import {
Button,
ComboBox,
FieldError,
Header,
Input,
Label,
ListBox,
ListBoxItem,
ListBoxSection,
Popover,
Text
} from 'react-aria-components';
<ComboBox>
<Label />
<Input />
<Button />
<Text slot="description" />
<FieldError />
<Popover>
<ListBox>
<ListBoxItem>
<Text slot="label" />
<Text slot="description" />
</ListBoxItem>
<ListBoxSection>
<Header />
<ListBoxItem />
</ListBoxSection>
</ListBox>
</Popover>
</ComboBox>
import {
Button,
ComboBox,
FieldError,
Header,
Input,
Label,
ListBox,
ListBoxItem,
ListBoxSection,
Popover,
Text
} from 'react-aria-components';
<ComboBox>
<Label />
<Input />
<Button />
<Text slot="description" />
<FieldError />
<Popover>
<ListBox>
<ListBoxItem>
<Text slot="label" />
<Text slot="description" />
</ListBoxItem>
<ListBoxSection>
<Header />
<ListBoxItem />
</ListBoxSection>
</ListBox>
</Popover>
</ComboBox>
If the combo box does not have a visible label, an aria-label
or aria-labelledby
prop must be passed instead to
identify it to assistive technology.
Concepts#
ComboBox
makes use of the following concepts:
Composed components#
A ComboBox
uses the following components, which may also be used standalone or reused in other components.
Examples#
Starter kits#
To help kick-start your project, we offer starter kits that include example implementations of all React Aria components with various styling solutions. All components are fully styled, including support for dark mode, high contrast mode, and all UI states. Each starter comes with a pre-configured Storybook that you can experiment with, or use as a starting point for your own component library.
Reusable wrappers#
If you will use a ComboBox in multiple places in your app, you can wrap all of the pieces into a reusable component. This way, the DOM structure, styling code, and other logic are defined in a single place and reused everywhere to ensure consistency.
This example wraps ComboBox
and all of its children together into a single component which accepts a label
prop and children
, which are passed through to the right places. It also shows how to use the description
slot to render help text, and FieldError
component to render validation errors. The Item
component is also wrapped to apply class names based on the current state, as described in the styling section.
import type {ComboBoxProps, ListBoxItemProps, ValidationResult} from 'react-aria-components';
import {FieldError, Text} from 'react-aria-components';
interface MyComboBoxProps<T extends object>
extends Omit<ComboBoxProps<T>, 'children'> {
label?: string;
description?: string | null;
errorMessage?: string | ((validation: ValidationResult) => string);
children: React.ReactNode | ((item: T) => React.ReactNode);
}
function MyComboBox<T extends object>(
{ label, description, errorMessage, children, ...props }: MyComboBoxProps<T>
) {
return (
<ComboBox {...props}>
<Label>{label}</Label>
<div className="my-combobox-container">
<Input />
<Button>▼</Button>
</div>
{description && <Text slot="description">{description}</Text>}
<FieldError>{errorMessage}</FieldError>
<Popover>
<ListBox>
{children}
</ListBox>
</Popover>
</ComboBox>
);
}
function MyItem(props: ListBoxItemProps) {
return (
<ListBoxItem
{...props}
className={({ isFocused, isSelected }) =>
`my-item `}
/>
);
}
<MyComboBox label="Ice cream flavor">
<MyItem>Chocolate</MyItem>
<MyItem>Mint</MyItem>
<MyItem>Strawberry</MyItem>
<MyItem>Vanilla</MyItem>
</MyComboBox>
import type {
ComboBoxProps,
ListBoxItemProps,
ValidationResult
} from 'react-aria-components';
import {FieldError, Text} from 'react-aria-components';
interface MyComboBoxProps<T extends object>
extends Omit<ComboBoxProps<T>, 'children'> {
label?: string;
description?: string | null;
errorMessage?:
| string
| ((validation: ValidationResult) => string);
children:
| React.ReactNode
| ((item: T) => React.ReactNode);
}
function MyComboBox<T extends object>(
{ label, description, errorMessage, children, ...props }:
MyComboBoxProps<T>
) {
return (
<ComboBox {...props}>
<Label>{label}</Label>
<div className="my-combobox-container">
<Input />
<Button>▼</Button>
</div>
{description && (
<Text slot="description">{description}</Text>
)}
<FieldError>{errorMessage}</FieldError>
<Popover>
<ListBox>
{children}
</ListBox>
</Popover>
</ComboBox>
);
}
function MyItem(props: ListBoxItemProps) {
return (
<ListBoxItem
{...props}
className={({ isFocused, isSelected }) =>
`my-item `}
/>
);
}
<MyComboBox label="Ice cream flavor">
<MyItem>Chocolate</MyItem>
<MyItem>Mint</MyItem>
<MyItem>Strawberry</MyItem>
<MyItem>Vanilla</MyItem>
</MyComboBox>
import type {
ComboBoxProps,
ListBoxItemProps,
ValidationResult
} from 'react-aria-components';
import {
FieldError,
Text
} from 'react-aria-components';
interface MyComboBoxProps<
T extends object
> extends
Omit<
ComboBoxProps<T>,
'children'
> {
label?: string;
description?:
| string
| null;
errorMessage?:
| string
| ((
validation:
ValidationResult
) => string);
children:
| React.ReactNode
| ((
item: T
) =>
React.ReactNode);
}
function MyComboBox<
T extends object
>({
label,
description,
errorMessage,
children,
...props
}: MyComboBoxProps<T>) {
return (
<ComboBox {...props}>
<Label>
{label}
</Label>
<div className="my-combobox-container">
<Input />
<Button>
▼
</Button>
</div>
{description && (
<Text slot="description">
{description}
</Text>
)}
<FieldError>
{errorMessage}
</FieldError>
<Popover>
<ListBox>
{children}
</ListBox>
</Popover>
</ComboBox>
);
}
function MyItem(
props: ListBoxItemProps
) {
return (
<ListBoxItem
{...props}
className={(
{
isFocused,
isSelected
}
) =>
`my-item `}
/>
);
}
<MyComboBox label="Ice cream flavor">
<MyItem>
Chocolate
</MyItem>
<MyItem>Mint</MyItem>
<MyItem>
Strawberry
</MyItem>
<MyItem>
Vanilla
</MyItem>
</MyComboBox>
Show CSS
.my-item {
margin: 2px;
padding: 4px 8px 4px 22px;
border-radius: 6px;
outline: none;
cursor: default;
color: var(--text-color);
font-size: 1.072rem;
position: relative;
&.selected {
font-weight: 600;
background: none;
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&.focused {
background: #e70073;
color: white;
}
}
@media (forced-colors: active) {
.my-item.focused {
background: Highlight;
color: HighlightText;
}
}
.my-item {
margin: 2px;
padding: 4px 8px 4px 22px;
border-radius: 6px;
outline: none;
cursor: default;
color: var(--text-color);
font-size: 1.072rem;
position: relative;
&.selected {
font-weight: 600;
background: none;
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&.focused {
background: #e70073;
color: white;
}
}
@media (forced-colors: active) {
.my-item.focused {
background: Highlight;
color: HighlightText;
}
}
.my-item {
margin: 2px;
padding: 4px 8px 4px 22px;
border-radius: 6px;
outline: none;
cursor: default;
color: var(--text-color);
font-size: 1.072rem;
position: relative;
&.selected {
font-weight: 600;
background: none;
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&.focused {
background: #e70073;
color: white;
}
}
@media (forced-colors: active) {
.my-item.focused {
background: Highlight;
color: HighlightText;
}
}
Content#
ComboBox 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 ComboBox using the defaultItems
prop. Each item accepts an id
prop, which
is passed to the onSelectionChange
handler to identify the selected item. Alternatively, if the item objects contain an id
property,
as shown in the example below, then this is used automatically and an id
prop is not required.
import type {Key} from 'react-aria-components';
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 [majorId, setMajorId] = React.useState<Key | null>(null);
return (
<>
<MyComboBox defaultItems={options} onSelectionChange={setMajorId}>
{(item) => <ListBoxItem>{item.name}</ListBoxItem>}
</MyComboBox>
<p>Selected topic id: {majorId}</p>
</>
);
}
import type {Key} from 'react-aria-components';
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 [majorId, setMajorId] = React.useState<Key | null>(
null
);
return (
<>
<MyComboBox
defaultItems={options}
onSelectionChange={setMajorId}
>
{(item) => <ListBoxItem>{item.name}</ListBoxItem>}
</MyComboBox>
<p>Selected topic id: {majorId}</p>
</>
);
}
import type {Key} from 'react-aria-components';
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 [
majorId,
setMajorId
] = React.useState<
Key | null
>(null);
return (
<>
<MyComboBox
defaultItems={options}
onSelectionChange={setMajorId}
>
{(item) => (
<ListBoxItem>
{item.name}
</ListBoxItem>
)}
</MyComboBox>
<p>
Selected topic
id: {majorId}
</p>
</>
);
}
Value#
A ComboBox's value
is empty by default, but an initial, uncontrolled, value can be provided using the defaultInputValue
prop.
Alternatively, a controlled value can be provided using the inputValue
prop. Note that the input value of the ComboBox does not affect
the ComboBox's selected option.
function Example() {
let options = [
{id: 1, name: 'Adobe Photoshop'},
{id: 2, name: 'Adobe XD'},
{id: 3, name: 'Adobe InDesign'},
{id: 4, name: 'Adobe AfterEffects'},
{id: 5, name: 'Adobe Illustrator'},
{id: 6, name: 'Adobe Lightroom'},
{id: 7, name: 'Adobe Premiere Pro'},
{id: 8, name: 'Adobe Fresco'},
{id: 9, name: 'Adobe Dreamweaver'}
];
let [value, setValue] = React.useState('Adobe XD');
return (
<div style={{display: 'flex', gap: 16, flexWrap: 'wrap'}}>
<MyComboBox
label="Adobe product (Uncontrolled)"
defaultItems={options}
defaultInputValue="Adobe XD"> {item => <ListBoxItem>{item.name}</ListBoxItem>}
</MyComboBox>
<MyComboBox
label="Pick an Adobe product (Controlled)"
defaultItems={options}
inputValue={value}
onInputChange={setValue}> {item => <ListBoxItem>{item.name}</ListBoxItem>}
</MyComboBox>
</div>
);
}
function Example() {
let options = [
{ id: 1, name: 'Adobe Photoshop' },
{ id: 2, name: 'Adobe XD' },
{ id: 3, name: 'Adobe InDesign' },
{ id: 4, name: 'Adobe AfterEffects' },
{ id: 5, name: 'Adobe Illustrator' },
{ id: 6, name: 'Adobe Lightroom' },
{ id: 7, name: 'Adobe Premiere Pro' },
{ id: 8, name: 'Adobe Fresco' },
{ id: 9, name: 'Adobe Dreamweaver' }
];
let [value, setValue] = React.useState('Adobe XD');
return (
<div
style={{ display: 'flex', gap: 16, flexWrap: 'wrap' }}
>
<MyComboBox
label="Adobe product (Uncontrolled)"
defaultItems={options}
defaultInputValue="Adobe XD"
> {(item) => <ListBoxItem>{item.name}</ListBoxItem>}
</MyComboBox>
<MyComboBox
label="Pick an Adobe product (Controlled)"
defaultItems={options}
inputValue={value}
onInputChange={setValue}
> {(item) => <ListBoxItem>{item.name}</ListBoxItem>}
</MyComboBox>
</div>
);
}
function Example() {
let options = [
{
id: 1,
name:
'Adobe Photoshop'
},
{
id: 2,
name: 'Adobe XD'
},
{
id: 3,
name:
'Adobe InDesign'
},
{
id: 4,
name:
'Adobe AfterEffects'
},
{
id: 5,
name:
'Adobe Illustrator'
},
{
id: 6,
name:
'Adobe Lightroom'
},
{
id: 7,
name:
'Adobe Premiere Pro'
},
{
id: 8,
name:
'Adobe Fresco'
},
{
id: 9,
name:
'Adobe Dreamweaver'
}
];
let [value, setValue] =
React.useState(
'Adobe XD'
);
return (
<div
style={{
display: 'flex',
gap: 16,
flexWrap: 'wrap'
}}
>
<MyComboBox
label="Adobe product (Uncontrolled)"
defaultItems={options}
defaultInputValue="Adobe XD"
> {(item) => (
<ListBoxItem>
{item.name}
</ListBoxItem>
)}
</MyComboBox>
<MyComboBox
label="Pick an Adobe product (Controlled)"
defaultItems={options}
inputValue={value}
onInputChange={setValue}
> {(item) => (
<ListBoxItem>
{item.name}
</ListBoxItem>
)}
</MyComboBox>
</div>
);
}
Custom values#
By default, ComboBox
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.
<MyComboBox label="Favorite Animal" allowsCustomValue>
<ListBoxItem id="red panda">Red Panda</ListBoxItem>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
<ListBoxItem id="aardvark">Aardvark</ListBoxItem>
<ListBoxItem id="kangaroo">Kangaroo</ListBoxItem>
<ListBoxItem id="snake">Snake</ListBoxItem>
</MyComboBox>
<MyComboBox label="Favorite Animal" allowsCustomValue>
<ListBoxItem id="red panda">Red Panda</ListBoxItem>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
<ListBoxItem id="aardvark">Aardvark</ListBoxItem>
<ListBoxItem id="kangaroo">Kangaroo</ListBoxItem>
<ListBoxItem id="snake">Snake</ListBoxItem>
</MyComboBox>
<MyComboBox
label="Favorite Animal"
allowsCustomValue
>
<ListBoxItem id="red panda">
Red Panda
</ListBoxItem>
<ListBoxItem id="cat">
Cat
</ListBoxItem>
<ListBoxItem id="dog">
Dog
</ListBoxItem>
<ListBoxItem id="aardvark">
Aardvark
</ListBoxItem>
<ListBoxItem id="kangaroo">
Kangaroo
</ListBoxItem>
<ListBoxItem id="snake">
Snake
</ListBoxItem>
</MyComboBox>
HTML forms#
ComboBox supports the name
prop for integration with HTML forms. By default, the id
of the selected item will be submitted to the server. If the formValue
prop is set to "text"
or the allowsCustomValue
prop is true, the text in the input field will be submitted instead.
<div style={{display: 'flex', gap: 16, flexWrap: 'wrap'}}>
<MyComboBox
label="Favorite Animal"
name="favoriteAnimalId" >
<ListBoxItem id="panda">Panda</ListBoxItem>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
</MyComboBox>
<MyComboBox
label="Ice cream flavor"
name="iceCream"
formValue="text"
allowsCustomValue >
<ListBoxItem>Chocolate</ListBoxItem>
<ListBoxItem>Mint</ListBoxItem>
<ListBoxItem>Strawberry</ListBoxItem>
<ListBoxItem>Vanilla</ListBoxItem>
</MyComboBox>
</div>
<div style={{display: 'flex', gap: 16, flexWrap: 'wrap'}}>
<MyComboBox
label="Favorite Animal"
name="favoriteAnimalId" >
<ListBoxItem id="panda">Panda</ListBoxItem>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
</MyComboBox>
<MyComboBox
label="Ice cream flavor"
name="iceCream"
formValue="text"
allowsCustomValue >
<ListBoxItem>Chocolate</ListBoxItem>
<ListBoxItem>Mint</ListBoxItem>
<ListBoxItem>Strawberry</ListBoxItem>
<ListBoxItem>Vanilla</ListBoxItem>
</MyComboBox>
</div>
<div
style={{
display: 'flex',
gap: 16,
flexWrap: 'wrap'
}}
>
<MyComboBox
label="Favorite Animal"
name="favoriteAnimalId" >
<ListBoxItem id="panda">
Panda
</ListBoxItem>
<ListBoxItem id="cat">
Cat
</ListBoxItem>
<ListBoxItem id="dog">
Dog
</ListBoxItem>
</MyComboBox>
<MyComboBox
label="Ice cream flavor"
name="iceCream"
formValue="text"
allowsCustomValue >
<ListBoxItem>
Chocolate
</ListBoxItem>
<ListBoxItem>
Mint
</ListBoxItem>
<ListBoxItem>
Strawberry
</ListBoxItem>
<ListBoxItem>
Vanilla
</ListBoxItem>
</MyComboBox>
</div>
Selection#
Setting a selected option can be done by using the defaultSelectedKey
or selectedKey
prop. The selected key corresponds to the id
of an item. See Events for more details on selection events.
function Example() {
let options = [
{id: 1, name: 'Adobe Photoshop'},
{id: 2, name: 'Adobe XD'},
{id: 3, name: 'Adobe InDesign'},
{id: 4, name: 'Adobe AfterEffects'},
{id: 5, name: 'Adobe Illustrator'},
{id: 6, name: 'Adobe Lightroom'},
{id: 7, name: 'Adobe Premiere Pro'},
{id: 8, name: 'Adobe Fresco'},
{id: 9, name: 'Adobe Dreamweaver'}
];
let [productId, setProductId] = React.useState<Key>(9);
return (
<div style={{display: 'flex', gap: 16, flexWrap: 'wrap'}}>
<MyComboBox
label="Pick an Adobe product (uncontrolled)"
defaultItems={options}
defaultSelectedKey={9} >
{item => <ListBoxItem>{item.name}</ListBoxItem>}
</MyComboBox>
<MyComboBox
label="Pick an Adobe product (controlled)"
defaultItems={options}
selectedKey={productId}
onSelectionChange={selected => setProductId(selected)} >
{item => <ListBoxItem>{item.name}</ListBoxItem>}
</MyComboBox>
</div>
);
}
function Example() {
let options = [
{ id: 1, name: 'Adobe Photoshop' },
{ id: 2, name: 'Adobe XD' },
{ id: 3, name: 'Adobe InDesign' },
{ id: 4, name: 'Adobe AfterEffects' },
{ id: 5, name: 'Adobe Illustrator' },
{ id: 6, name: 'Adobe Lightroom' },
{ id: 7, name: 'Adobe Premiere Pro' },
{ id: 8, name: 'Adobe Fresco' },
{ id: 9, name: 'Adobe Dreamweaver' }
];
let [productId, setProductId] = React.useState<Key>(9);
return (
<div
style={{ display: 'flex', gap: 16, flexWrap: 'wrap' }}
>
<MyComboBox
label="Pick an Adobe product (uncontrolled)"
defaultItems={options}
defaultSelectedKey={9} >
{(item) => <ListBoxItem>{item.name}</ListBoxItem>}
</MyComboBox>
<MyComboBox
label="Pick an Adobe product (controlled)"
defaultItems={options}
selectedKey={productId}
onSelectionChange={(selected) =>
setProductId(selected)} >
{(item) => <ListBoxItem>{item.name}</ListBoxItem>}
</MyComboBox>
</div>
);
}
function Example() {
let options = [
{
id: 1,
name:
'Adobe Photoshop'
},
{
id: 2,
name: 'Adobe XD'
},
{
id: 3,
name:
'Adobe InDesign'
},
{
id: 4,
name:
'Adobe AfterEffects'
},
{
id: 5,
name:
'Adobe Illustrator'
},
{
id: 6,
name:
'Adobe Lightroom'
},
{
id: 7,
name:
'Adobe Premiere Pro'
},
{
id: 8,
name:
'Adobe Fresco'
},
{
id: 9,
name:
'Adobe Dreamweaver'
}
];
let [
productId,
setProductId
] = React.useState<
Key
>(9);
return (
<div
style={{
display: 'flex',
gap: 16,
flexWrap: 'wrap'
}}
>
<MyComboBox
label="Pick an Adobe product (uncontrolled)"
defaultItems={options}
defaultSelectedKey={9} >
{(item) => (
<ListBoxItem>
{item.name}
</ListBoxItem>
)}
</MyComboBox>
<MyComboBox
label="Pick an Adobe product (controlled)"
defaultItems={options}
selectedKey={productId}
onSelectionChange={(selected) =>
setProductId(
selected
)} >
{(item) => (
<ListBoxItem>
{item.name}
</ListBoxItem>
)}
</MyComboBox>
</div>
);
}
Links#
By default, interacting with an item in a ComboBox selects it and updates the input value. Alternatively, items may be links to another page or website. This can be achieved by passing the href
prop to the <ListBoxItem>
component. Interacting with link items navigates to the provided URL and does not update the selection or input value.
<MyComboBox label="Tech company websites">
<ListBoxItem href="https://adobe.com/" target="_blank">Adobe</ListBoxItem>
<ListBoxItem href="https://apple.com/" target="_blank">Apple</ListBoxItem>
<ListBoxItem href="https://google.com/" target="_blank">Google</ListBoxItem>
<ListBoxItem href="https://microsoft.com/" target="_blank">
Microsoft
</ListBoxItem>
</MyComboBox>
<MyComboBox label="Tech company websites">
<ListBoxItem href="https://adobe.com/" target="_blank">
Adobe
</ListBoxItem>
<ListBoxItem href="https://apple.com/" target="_blank">
Apple
</ListBoxItem>
<ListBoxItem href="https://google.com/" target="_blank">
Google
</ListBoxItem>
<ListBoxItem
href="https://microsoft.com/"
target="_blank"
>
Microsoft
</ListBoxItem>
</MyComboBox>
<MyComboBox label="Tech company websites">
<ListBoxItem
href="https://adobe.com/"
target="_blank"
>
Adobe
</ListBoxItem>
<ListBoxItem
href="https://apple.com/"
target="_blank"
>
Apple
</ListBoxItem>
<ListBoxItem
href="https://google.com/"
target="_blank"
>
Google
</ListBoxItem>
<ListBoxItem
href="https://microsoft.com/"
target="_blank"
>
Microsoft
</ListBoxItem>
</MyComboBox>
Client side routing#
The <ListBoxItem>
component works with frameworks and client side routers like Next.js and React Router. As with other React Aria components that support links, this works via the RouterProvider
component at the root of your app. See the client side routing guide to learn how to set this up.
Sections#
ComboBox supports sections in order to group options. Sections can be used by wrapping groups of items in a ListBoxSection
element. A <Header>
element may also be included to label the section.
Static items#
import {ListBoxSection, Header} from 'react-aria-components';
<MyComboBox label="Preferred fruit or vegetable">
<ListBoxSection>
<Header>Fruit</Header>
<ListBoxItem id="Apple">Apple</ListBoxItem>
<ListBoxItem id="Banana">Banana</ListBoxItem>
<ListBoxItem id="Orange">Orange</ListBoxItem>
<ListBoxItem id="Honeydew">Honeydew</ListBoxItem>
<ListBoxItem id="Grapes">Grapes</ListBoxItem>
<ListBoxItem id="Watermelon">Watermelon</ListBoxItem>
<ListBoxItem id="Cantaloupe">Cantaloupe</ListBoxItem>
<ListBoxItem id="Pear">Pear</ListBoxItem>
</ListBoxSection>
<ListBoxSection>
<Header>Vegetable</Header>
<ListBoxItem id="Cabbage">Cabbage</ListBoxItem>
<ListBoxItem id="Broccoli">Broccoli</ListBoxItem>
<ListBoxItem id="Carrots">Carrots</ListBoxItem>
<ListBoxItem id="Lettuce">Lettuce</ListBoxItem>
<ListBoxItem id="Spinach">Spinach</ListBoxItem>
<ListBoxItem id="Bok Choy">Bok Choy</ListBoxItem>
<ListBoxItem id="Cauliflower">Cauliflower</ListBoxItem>
<ListBoxItem id="Potatoes">Potatoes</ListBoxItem>
</ListBoxSection>
</MyComboBox>
import {
Header,
ListBoxSection
} from 'react-aria-components';
<MyComboBox label="Preferred fruit or vegetable">
<ListBoxSection>
<Header>Fruit</Header>
<ListBoxItem id="Apple">Apple</ListBoxItem>
<ListBoxItem id="Banana">Banana</ListBoxItem>
<ListBoxItem id="Orange">Orange</ListBoxItem>
<ListBoxItem id="Honeydew">Honeydew</ListBoxItem>
<ListBoxItem id="Grapes">Grapes</ListBoxItem>
<ListBoxItem id="Watermelon">Watermelon</ListBoxItem>
<ListBoxItem id="Cantaloupe">Cantaloupe</ListBoxItem>
<ListBoxItem id="Pear">Pear</ListBoxItem>
</ListBoxSection>
<ListBoxSection>
<Header>Vegetable</Header>
<ListBoxItem id="Cabbage">Cabbage</ListBoxItem>
<ListBoxItem id="Broccoli">Broccoli</ListBoxItem>
<ListBoxItem id="Carrots">Carrots</ListBoxItem>
<ListBoxItem id="Lettuce">Lettuce</ListBoxItem>
<ListBoxItem id="Spinach">Spinach</ListBoxItem>
<ListBoxItem id="Bok Choy">Bok Choy</ListBoxItem>
<ListBoxItem id="Cauliflower">
Cauliflower
</ListBoxItem>
<ListBoxItem id="Potatoes">Potatoes</ListBoxItem>
</ListBoxSection>
</MyComboBox>
import {
Header,
ListBoxSection
} from 'react-aria-components';
<MyComboBox label="Preferred fruit or vegetable">
<ListBoxSection>
<Header>
Fruit
</Header>
<ListBoxItem id="Apple">
Apple
</ListBoxItem>
<ListBoxItem id="Banana">
Banana
</ListBoxItem>
<ListBoxItem id="Orange">
Orange
</ListBoxItem>
<ListBoxItem id="Honeydew">
Honeydew
</ListBoxItem>
<ListBoxItem id="Grapes">
Grapes
</ListBoxItem>
<ListBoxItem id="Watermelon">
Watermelon
</ListBoxItem>
<ListBoxItem id="Cantaloupe">
Cantaloupe
</ListBoxItem>
<ListBoxItem id="Pear">
Pear
</ListBoxItem>
</ListBoxSection>
<ListBoxSection>
<Header>
Vegetable
</Header>
<ListBoxItem id="Cabbage">
Cabbage
</ListBoxItem>
<ListBoxItem id="Broccoli">
Broccoli
</ListBoxItem>
<ListBoxItem id="Carrots">
Carrots
</ListBoxItem>
<ListBoxItem id="Lettuce">
Lettuce
</ListBoxItem>
<ListBoxItem id="Spinach">
Spinach
</ListBoxItem>
<ListBoxItem id="Bok Choy">
Bok Choy
</ListBoxItem>
<ListBoxItem id="Cauliflower">
Cauliflower
</ListBoxItem>
<ListBoxItem id="Potatoes">
Potatoes
</ListBoxItem>
</ListBoxSection>
</MyComboBox>
Dynamic items#
Sections used with dynamic items are populated from a hierarchical data structure. Please note that ListBoxSection
takes an array of data using the items
prop only. If the section also has a header,
the Collection
component can be used to render the child items.
import {Collection} from 'react-aria-components';
function Example() {
let options = [
{name: 'Fruit', children: [
{name: 'Apple'},
{name: 'Banana'},
{name: 'Orange'},
{name: 'Honeydew'},
{name: 'Grapes'},
{name: 'Watermelon'},
{name: 'Cantaloupe'},
{name: 'Pear'}
]},
{name: 'Vegetable', children: [
{name: 'Cabbage'},
{name: 'Broccoli'},
{name: 'Carrots'},
{name: 'Lettuce'},
{name: 'Spinach'},
{name: 'Bok Choy'},
{name: 'Cauliflower'},
{name: 'Potatoes'}
]}
];
return (
<MyComboBox label="Preferred fruit or vegetable" defaultItems={options}>
{section => (
<ListBoxSection id={section.name}>
<Header>{section.name}</Header>
<Collection items={section.children}>
{item => <ListBoxItem id={item.name}>{item.name}</ListBoxItem>}
</Collection>
</ListBoxSection>
)}
</MyComboBox>
);
}
import {Collection} from 'react-aria-components';
function Example() {
let options = [
{
name: 'Fruit',
children: [
{ name: 'Apple' },
{ name: 'Banana' },
{ name: 'Orange' },
{ name: 'Honeydew' },
{ name: 'Grapes' },
{ name: 'Watermelon' },
{ name: 'Cantaloupe' },
{ name: 'Pear' }
]
},
{
name: 'Vegetable',
children: [
{ name: 'Cabbage' },
{ name: 'Broccoli' },
{ name: 'Carrots' },
{ name: 'Lettuce' },
{ name: 'Spinach' },
{ name: 'Bok Choy' },
{ name: 'Cauliflower' },
{ name: 'Potatoes' }
]
}
];
return (
<MyComboBox
label="Preferred fruit or vegetable"
defaultItems={options}
>
{(section) => (
<ListBoxSection id={section.name}>
<Header>{section.name}</Header>
<Collection items={section.children}>
{(item) => (
<ListBoxItem id={item.name}>
{item.name}
</ListBoxItem>
)}
</Collection>
</ListBoxSection>
)}
</MyComboBox>
);
}
import {Collection} from 'react-aria-components';
function Example() {
let options = [
{
name: 'Fruit',
children: [
{
name: 'Apple'
},
{
name: 'Banana'
},
{
name: 'Orange'
},
{
name:
'Honeydew'
},
{
name: 'Grapes'
},
{
name:
'Watermelon'
},
{
name:
'Cantaloupe'
},
{ name: 'Pear' }
]
},
{
name: 'Vegetable',
children: [
{
name: 'Cabbage'
},
{
name:
'Broccoli'
},
{
name: 'Carrots'
},
{
name: 'Lettuce'
},
{
name: 'Spinach'
},
{
name:
'Bok Choy'
},
{
name:
'Cauliflower'
},
{
name:
'Potatoes'
}
]
}
];
return (
<MyComboBox
label="Preferred fruit or vegetable"
defaultItems={options}
>
{(section) => (
<ListBoxSection
id={section
.name}
>
<Header>
{section
.name}
</Header>
<Collection
items={section
.children}
>
{(item) => (
<ListBoxItem
id={item
.name}
>
{item
.name}
</ListBoxItem>
)}
</Collection>
</ListBoxSection>
)}
</MyComboBox>
);
}
Text slots#
By default, items in a ComboBox are labeled by their text contents for accessibility. ListBoxItems also support the "label" and "description" slots to separate primary and secondary content, which improves screen reader announcements and can also be used for styling purposes.
Note: The ARIA spec prohibits listbox items from including interactive content such as buttons, checkboxes, etc.
import {Text} from 'react-aria-components';
<MyComboBox label="Select action">
<ListBoxItem textValue="Add to queue">
<Text slot="label">Add to queue</Text>
<Text slot="description">Add to current watch queue.</Text>
</ListBoxItem>
<ListBoxItem textValue="Add review">
<Text slot="label">Add review</Text>
<Text slot="description">Post a review for the episode.</Text>
</ListBoxItem>
<ListBoxItem textValue="Subscribe to series">
<Text slot="label">Subscribe to series</Text>
<Text slot="description">
Add series to your subscription list and be notified when a new episode
airs.
</Text>
</ListBoxItem>
<ListBoxItem textValue="Report">
<Text slot="label">Report</Text>
<Text slot="description">Report an issue/violation.</Text>
</ListBoxItem>
</MyComboBox>
import {Text} from 'react-aria-components';
<MyComboBox label="Select action">
<ListBoxItem textValue="Add to queue">
<Text slot="label">Add to queue</Text>
<Text slot="description">
Add to current watch queue.
</Text>
</ListBoxItem>
<ListBoxItem textValue="Add review">
<Text slot="label">Add review</Text>
<Text slot="description">
Post a review for the episode.
</Text>
</ListBoxItem>
<ListBoxItem textValue="Subscribe to series">
<Text slot="label">Subscribe to series</Text>
<Text slot="description">
Add series to your subscription list and be notified
when a new episode airs.
</Text>
</ListBoxItem>
<ListBoxItem textValue="Report">
<Text slot="label">Report</Text>
<Text slot="description">
Report an issue/violation.
</Text>
</ListBoxItem>
</MyComboBox>
import {Text} from 'react-aria-components';
<MyComboBox label="Select action">
<ListBoxItem textValue="Add to queue">
<Text slot="label">
Add to queue
</Text>
<Text slot="description">
Add to current
watch queue.
</Text>
</ListBoxItem>
<ListBoxItem textValue="Add review">
<Text slot="label">
Add review
</Text>
<Text slot="description">
Post a review for
the episode.
</Text>
</ListBoxItem>
<ListBoxItem textValue="Subscribe to series">
<Text slot="label">
Subscribe to
series
</Text>
<Text slot="description">
Add series to
your subscription
list and be
notified when a
new episode airs.
</Text>
</ListBoxItem>
<ListBoxItem textValue="Report">
<Text slot="label">
Report
</Text>
<Text slot="description">
Report an
issue/violation.
</Text>
</ListBoxItem>
</MyComboBox>
Events#
ComboBox supports selection via mouse, keyboard, and touch. You can handle all of these via the onSelectionChange
prop. ComboBox will pass the selected id
to the onSelectionChange
handler.
Additionally, ComboBox accepts an onInputChange
prop which is triggered whenever the value is edited by the user, whether through typing or option
selection.
The example below uses onSelectionChange
and onInputChange
to update the selection and input value stored in React state.
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 [value, setValue] = React.useState('');
let [majorId, setMajorId] = React.useState('');
let onSelectionChange = (id) => {
setMajorId(id);
};
let onInputChange = (value) => {
setValue(value)
};
return (
<>
<p>Current selected major id: {majorId}</p>
<p>Current input text: {value}</p>
<MyComboBox
label="Pick a engineering major"
defaultItems={options}
selectedKey={majorId}
onSelectionChange={onSelectionChange}
onInputChange={onInputChange} >
{item => <ListBoxItem>{item.name}</ListBoxItem>}
</MyComboBox>
</>
);
}
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 [value, setValue] = React.useState('');
let [majorId, setMajorId] = React.useState('');
let onSelectionChange = (id) => {
setMajorId(id);
};
let onInputChange = (value) => {
setValue(value)
};
return (
<>
<p>Current selected major id: {majorId}</p>
<p>Current input text: {value}</p>
<MyComboBox
label="Pick a engineering major"
defaultItems={options}
selectedKey={majorId}
onSelectionChange={onSelectionChange}
onInputChange={onInputChange} >
{item => <ListBoxItem>{item.name}</ListBoxItem>}
</MyComboBox>
</>
);
}
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 [value, setValue] =
React.useState('');
let [
majorId,
setMajorId
] = React.useState('');
let onSelectionChange =
(id) => {
setMajorId(id);
};
let onInputChange = (
value
) => {
setValue(value);
};
return (
<>
<p>
Current selected
major id:{' '}
{majorId}
</p>
<p>
Current input
text: {value}
</p>
<MyComboBox
label="Pick a engineering major"
defaultItems={options}
selectedKey={majorId}
onSelectionChange={onSelectionChange}
onInputChange={onInputChange} >
{(item) => (
<ListBoxItem>
{item.name}
</ListBoxItem>
)}
</MyComboBox>
</>
);
}
Custom filtering#
By default, ComboBox
uses a "contains" function from useFilter to filter the list of options. This can be overridden using the defaultFilter
prop, or by using the items
prop to control the filtered list. When items
is provided rather than defaultItems
, ComboBox
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.
import {useFilter} from 'react-aria';
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] = React.useState('');
let filteredItems = React.useMemo(
() => options.filter((item) => startsWith(item.email, filterValue)),
[options, filterValue]
);
return (
<MyComboBox
label="To:"
items={filteredItems}
inputValue={filterValue}
onInputChange={setFilterValue} allowsCustomValue
>
{(item) => <ListBoxItem>{item.email}</ListBoxItem>}
</MyComboBox>
);
}
import {useFilter} from 'react-aria';
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] = React.useState('');
let filteredItems = React.useMemo(
() =>
options.filter((item) =>
startsWith(item.email, filterValue)
),
[options, filterValue]
);
return (
<MyComboBox
label="To:"
items={filteredItems}
inputValue={filterValue}
onInputChange={setFilterValue} allowsCustomValue
>
{(item) => <ListBoxItem>{item.email}</ListBoxItem>}
</MyComboBox>
);
}
import {useFilter} from 'react-aria';
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
] = React.useState('');
let filteredItems =
React.useMemo(
() =>
options.filter((
item
) =>
startsWith(
item.email,
filterValue
)
),
[
options,
filterValue
]
);
return (
<MyComboBox
label="To:"
items={filteredItems}
inputValue={filterValue}
onInputChange={setFilterValue} allowsCustomValue
>
{(item) => (
<ListBoxItem>
{item.email}
</ListBoxItem>
)}
</MyComboBox>
);
}
Fully controlled#
When a ComboBox has multiple controlled properties (e.g.inputValue
, selectedKey
, items
), it is important to note that an update to one of these properties will
not automatically update the others. Each interaction done in the ComboBox will only trigger its associated event handler. For example, typing in the field will only
trigger onInputChange
whereas selecting an item from the ComboBox menu will only trigger onSelectionChange
so it is your responsibility to update the other
controlled properties accordingly. Note that you should provide an onSelectionChange
handler for a ComboBox with controlled input value and open state. This way, you can properly
control the menu's open state when the user selects an option or blurs from the field.
The below example demonstrates how you would construct the same example above in a completely controlled fashion.
function ControlledComboBox() {
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 [fieldState, setFieldState] = React.useState({
selectedKey: null,
inputValue: ''
});
let onSelectionChange = (id: Key) => {
setFieldState({
inputValue: options.find(o => o.id === id)?.name ?? '',
selectedKey: id
});
};
let onInputChange = (value: string) => {
setFieldState(prevState => ({
inputValue: value,
selectedKey: value === '' ? null : prevState.selectedKey
}));
};
return (
<>
<p>Current selected major id: {fieldState.selectedKey}</p>
<p>Current input text: {fieldState.inputValue}</p>
<MyComboBox
label="Pick a engineering major"
defaultItems={options}
selectedKey={fieldState.selectedKey}
inputValue={fieldState.inputValue}
onSelectionChange={onSelectionChange}
onInputChange={onInputChange}>
{item => <ListBoxItem>{item.name}</ListBoxItem>}
</MyComboBox>
</>
);
}
function ControlledComboBox() {
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 [fieldState, setFieldState] = React.useState({
selectedKey: null,
inputValue: ''
});
let onSelectionChange = (id: Key) => {
setFieldState({
inputValue: options.find((o) => o.id === id)?.name ??
'',
selectedKey: id
});
};
let onInputChange = (value: string) => {
setFieldState((prevState) => ({
inputValue: value,
selectedKey: value === ''
? null
: prevState.selectedKey
}));
};
return (
<>
<p>
Current selected major id: {fieldState.selectedKey}
</p>
<p>Current input text: {fieldState.inputValue}</p>
<MyComboBox
label="Pick a engineering major"
defaultItems={options}
selectedKey={fieldState.selectedKey}
inputValue={fieldState.inputValue}
onSelectionChange={onSelectionChange}
onInputChange={onInputChange}
>
{(item) => <ListBoxItem>{item.name}</ListBoxItem>}
</MyComboBox>
</>
);
}
function ControlledComboBox() {
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 [
fieldState,
setFieldState
] = React.useState({
selectedKey: null,
inputValue: ''
});
let onSelectionChange =
(id: Key) => {
setFieldState({
inputValue:
options.find(
(o) =>
o.id === id
)?.name ?? '',
selectedKey: id
});
};
let onInputChange = (
value: string
) => {
setFieldState(
(prevState) => ({
inputValue:
value,
selectedKey:
value === ''
? null
: prevState
.selectedKey
})
);
};
return (
<>
<p>
Current selected
major id:{' '}
{fieldState
.selectedKey}
</p>
<p>
Current input
text:{' '}
{fieldState
.inputValue}
</p>
<MyComboBox
label="Pick a engineering major"
defaultItems={options}
selectedKey={fieldState
.selectedKey}
inputValue={fieldState
.inputValue}
onSelectionChange={onSelectionChange}
onInputChange={onInputChange}
>
{(item) => (
<ListBoxItem>
{item.name}
</ListBoxItem>
)}
</MyComboBox>
</>
);
}
Menu trigger behavior#
ComboBox
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 or uses the arrow keys.
The example below has menuTrigger
set to focus
.
<MyComboBox label="Favorite Animal" menuTrigger="focus">
<ListBoxItem id="red panda">Red Panda</ListBoxItem>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
<ListBoxItem id="aardvark">Aardvark</ListBoxItem>
<ListBoxItem id="kangaroo">Kangaroo</ListBoxItem>
<ListBoxItem id="snake">Snake</ListBoxItem>
</MyComboBox>
<MyComboBox label="Favorite Animal" menuTrigger="focus">
<ListBoxItem id="red panda">Red Panda</ListBoxItem>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
<ListBoxItem id="aardvark">Aardvark</ListBoxItem>
<ListBoxItem id="kangaroo">Kangaroo</ListBoxItem>
<ListBoxItem id="snake">Snake</ListBoxItem>
</MyComboBox>
<MyComboBox
label="Favorite Animal"
menuTrigger="focus"
>
<ListBoxItem id="red panda">
Red Panda
</ListBoxItem>
<ListBoxItem id="cat">
Cat
</ListBoxItem>
<ListBoxItem id="dog">
Dog
</ListBoxItem>
<ListBoxItem id="aardvark">
Aardvark
</ListBoxItem>
<ListBoxItem id="kangaroo">
Kangaroo
</ListBoxItem>
<ListBoxItem id="snake">
Snake
</ListBoxItem>
</MyComboBox>
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. See this CodeSandbox for an example of a ComboBox supporting those features.
import {useAsyncList} from 'react-stately';
interface Character {
name: string;
}
function AsyncLoadingExample() {
let list = useAsyncList<Character>({
async load({ signal, filterText }) {
let res = await fetch(
`https://swapi.py4e.com/api/people/?search= `,
{ signal }
);
let json = await res.json();
return {
items: json.results
};
}
});
return (
<MyComboBox
label="Star Wars Character Lookup"
items={list.items}
inputValue={list.filterText}
onInputChange={list.setFilterText}
>
{(item) => <ListBoxItem id={item.name}>{item.name}</ListBoxItem>}
</MyComboBox>
);
}
import {useAsyncList} from 'react-stately';
interface Character {
name: string;
}
function AsyncLoadingExample() {
let list = useAsyncList<Character>({
async load({ signal, filterText }) {
let res = await fetch(
`https://swapi.py4e.com/api/people/?search= `,
{ signal }
);
let json = await res.json();
return {
items: json.results
};
}
});
return (
<MyComboBox
label="Star Wars Character Lookup"
items={list.items}
inputValue={list.filterText}
onInputChange={list.setFilterText}
>
{(item) => (
<ListBoxItem id={item.name}>
{item.name}
</ListBoxItem>
)}
</MyComboBox>
);
}
import {useAsyncList} from 'react-stately';
interface Character {
name: string;
}
function AsyncLoadingExample() {
let list =
useAsyncList<
Character
>({
async load(
{
signal,
filterText
}
) {
let res =
await fetch(
`https://swapi.py4e.com/api/people/?search= `,
{ signal }
);
let json =
await res
.json();
return {
items:
json.results
};
}
});
return (
<MyComboBox
label="Star Wars Character Lookup"
items={list.items}
inputValue={list
.filterText}
onInputChange={list
.setFilterText}
>
{(item) => (
<ListBoxItem
id={item.name}
>
{item.name}
</ListBoxItem>
)}
</MyComboBox>
);
}
Disabled#
A ComboBox
can be fully disabled using the isDisabled prop.
<MyComboBox label="Favorite Animal" isDisabled>
<ListBoxItem id="red panda">Panda</ListBoxItem>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
</MyComboBox>
<MyComboBox label="Favorite Animal" isDisabled>
<ListBoxItem id="red panda">Panda</ListBoxItem>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
</MyComboBox>
<MyComboBox
label="Favorite Animal"
isDisabled
>
<ListBoxItem id="red panda">
Panda
</ListBoxItem>
<ListBoxItem id="cat">
Cat
</ListBoxItem>
<ListBoxItem id="dog">
Dog
</ListBoxItem>
</MyComboBox>
Show CSS
.react-aria-ComboBox {
.react-aria-Input {
&[data-disabled] {
border-color: var(--border-color-disabled);
}
}
.react-aria-Button {
&[data-disabled] {
background: var(--border-color-disabled);
}
}
}
.react-aria-ComboBox {
.react-aria-Input {
&[data-disabled] {
border-color: var(--border-color-disabled);
}
}
.react-aria-Button {
&[data-disabled] {
background: var(--border-color-disabled);
}
}
}
.react-aria-ComboBox {
.react-aria-Input {
&[data-disabled] {
border-color: var(--border-color-disabled);
}
}
.react-aria-Button {
&[data-disabled] {
background: var(--border-color-disabled);
}
}
}
Disabled options#
You can disable specific options by providing an array of keys to ComboBox
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.
<MyComboBox label="Favorite Animal" disabledKeys={['cat', 'kangaroo']}>
<ListBoxItem id="red panda">Red Panda</ListBoxItem>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
<ListBoxItem id="aardvark">Aardvark</ListBoxItem>
<ListBoxItem id="kangaroo">Kangaroo</ListBoxItem>
<ListBoxItem id="snake">Snake</ListBoxItem>
</MyComboBox>
<MyComboBox
label="Favorite Animal"
disabledKeys={['cat', 'kangaroo']}
>
<ListBoxItem id="red panda">Red Panda</ListBoxItem>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
<ListBoxItem id="aardvark">Aardvark</ListBoxItem>
<ListBoxItem id="kangaroo">Kangaroo</ListBoxItem>
<ListBoxItem id="snake">Snake</ListBoxItem>
</MyComboBox>
<MyComboBox
label="Favorite Animal"
disabledKeys={[
'cat',
'kangaroo'
]}
>
<ListBoxItem id="red panda">
Red Panda
</ListBoxItem>
<ListBoxItem id="cat">
Cat
</ListBoxItem>
<ListBoxItem id="dog">
Dog
</ListBoxItem>
<ListBoxItem id="aardvark">
Aardvark
</ListBoxItem>
<ListBoxItem id="kangaroo">
Kangaroo
</ListBoxItem>
<ListBoxItem id="snake">
Snake
</ListBoxItem>
</MyComboBox>
Validation#
ComboBox supports the isRequired
prop to ensure the user enters a value, as well as custom client and server-side validation. It can also be integrated with other form libraries. See the Forms guide to learn more.
To display validation errors, add a <FieldError>
element as a child of the ComboBox. This allows you to render error messages from all of the above sources with consistent custom styles.
import {Form, FieldError} from 'react-aria-components';
<Form>
<ComboBox name="animal" isRequired> <Label>Favorite Animal</Label>
<div>
<Input />
<Button>▼</Button>
</div>
<FieldError /> <Popover>
<ListBox>
<ListBoxItem>Aardvark</ListBoxItem>
<ListBoxItem>Cat</ListBoxItem>
<ListBoxItem>Dog</ListBoxItem>
<ListBoxItem>Kangaroo</ListBoxItem>
<ListBoxItem>Panda</ListBoxItem>
<ListBoxItem>Snake</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
<Button type="submit">Submit</Button>
</Form>
import {Form, FieldError} from 'react-aria-components';
<Form>
<ComboBox name="animal" isRequired> <Label>Favorite Animal</Label>
<div>
<Input />
<Button>▼</Button>
</div>
<FieldError /> <Popover>
<ListBox>
<ListBoxItem>Aardvark</ListBoxItem>
<ListBoxItem>Cat</ListBoxItem>
<ListBoxItem>Dog</ListBoxItem>
<ListBoxItem>Kangaroo</ListBoxItem>
<ListBoxItem>Panda</ListBoxItem>
<ListBoxItem>Snake</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
<Button type="submit">Submit</Button>
</Form>
import {
FieldError,
Form
} from 'react-aria-components';
<Form>
<ComboBox
name="animal"
isRequired
> <Label>
Favorite Animal
</Label>
<div>
<Input />
<Button>
▼
</Button>
</div>
<FieldError /> <Popover>
<ListBox>
<ListBoxItem>
Aardvark
</ListBoxItem>
<ListBoxItem>
Cat
</ListBoxItem>
<ListBoxItem>
Dog
</ListBoxItem>
<ListBoxItem>
Kangaroo
</ListBoxItem>
<ListBoxItem>
Panda
</ListBoxItem>
<ListBoxItem>
Snake
</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
<Button type="submit">
Submit
</Button>
</Form>
Show CSS
.react-aria-ComboBox {
.react-aria-Input {
&[data-invalid]:not([data-focused]) {
border-color: var(--invalid-color);
}
}
.react-aria-FieldError {
font-size: 12px;
color: var(--invalid-color);
}
}
.react-aria-ComboBox {
.react-aria-Input {
&[data-invalid]:not([data-focused]) {
border-color: var(--invalid-color);
}
}
.react-aria-FieldError {
font-size: 12px;
color: var(--invalid-color);
}
}
.react-aria-ComboBox {
.react-aria-Input {
&[data-invalid]:not([data-focused]) {
border-color: var(--invalid-color);
}
}
.react-aria-FieldError {
font-size: 12px;
color: var(--invalid-color);
}
}
By default, FieldError
displays default validation messages provided by the browser. See Customizing error messages in the Forms guide to learn how to provide your own custom errors.
Description#
The description
slot can be used to associate additional help text with a ComboBox.
<ComboBox>
<Label>Favorite Animal</Label>
<div>
<Input />
<Button>▼</Button>
</div>
<Text slot="description">Please select an animal.</Text> <Popover>
<ListBox>
<ListBoxItem>Aardvark</ListBoxItem>
<ListBoxItem>Cat</ListBoxItem>
<ListBoxItem>Dog</ListBoxItem>
<ListBoxItem>Kangaroo</ListBoxItem>
<ListBoxItem>Panda</ListBoxItem>
<ListBoxItem>Snake</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
<ComboBox>
<Label>Favorite Animal</Label>
<div>
<Input />
<Button>▼</Button>
</div>
<Text slot="description">Please select an animal.</Text> <Popover>
<ListBox>
<ListBoxItem>Aardvark</ListBoxItem>
<ListBoxItem>Cat</ListBoxItem>
<ListBoxItem>Dog</ListBoxItem>
<ListBoxItem>Kangaroo</ListBoxItem>
<ListBoxItem>Panda</ListBoxItem>
<ListBoxItem>Snake</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
<ComboBox>
<Label>
Favorite Animal
</Label>
<div>
<Input />
<Button>▼</Button>
</div>
<Text slot="description">
Please select an
animal.
</Text> <Popover>
<ListBox>
<ListBoxItem>
Aardvark
</ListBoxItem>
<ListBoxItem>
Cat
</ListBoxItem>
<ListBoxItem>
Dog
</ListBoxItem>
<ListBoxItem>
Kangaroo
</ListBoxItem>
<ListBoxItem>
Panda
</ListBoxItem>
<ListBoxItem>
Snake
</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
Show CSS
.react-aria-ComboBox {
[slot=description] {
font-size: 12px;
}
}
.react-aria-ComboBox {
[slot=description] {
font-size: 12px;
}
}
.react-aria-ComboBox {
[slot=description] {
font-size: 12px;
}
}
Props#
ComboBox#
Name | Type | Default | Description |
defaultFilter | (
(textValue: string,
, inputValue: string
)) => boolean | — | The filter function used to determine if a option should be included in the combo box list. |
formValue | 'text' | 'key' | 'key' | Whether the text or key of the selected item is submitted as part of an HTML form.
When |
allowsEmptyCollection | boolean | — | Whether the combo box allows the menu to be open when the collection is empty. |
shouldFocusWrap | boolean | — | Whether keyboard navigation is circular. |
defaultItems | Iterable<T> | — | The list of ComboBox items (uncontrolled). |
items | Iterable<T> | — | The list of ComboBox items (controlled). |
inputValue | string | — | The value of the ComboBox input (controlled). |
defaultInputValue | string | — | The default value of the ComboBox input (uncontrolled). |
allowsCustomValue | boolean | — | Whether the ComboBox allows a non-item matching input value to be set. |
menuTrigger | MenuTriggerAction | 'input' | The interaction required to display the ComboBox menu. |
disabledKeys | Iterable<Key> | — | The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with. |
selectedKey | Key | null | — | The currently selected key in the collection (controlled). |
defaultSelectedKey | Key | — | The initial selected key in the collection (uncontrolled). |
isDisabled | boolean | — | Whether the input is disabled. |
isReadOnly | boolean | — | Whether the input can be selected but not changed by the user. |
isRequired | boolean | — | Whether user input is required on the input before form submission. |
isInvalid | boolean | — | Whether the input value is invalid. |
validate | (
(value: ComboBoxValidationValue
)) => ValidationError
| true
| null
| undefined | — | A function that returns an error message if a given value is invalid.
Validation errors are displayed to the user when the form is submitted
if |
autoFocus | boolean | — | Whether the element should receive focus on render. |
name | string | — | The name of the input element, used when submitting an HTML form. See MDN. |
validationBehavior | 'native' | 'aria' | 'native' | Whether to use native HTML form validation to prevent form submission when the value is missing or invalid, or mark the field as required or invalid via ARIA. |
children | ReactNode | (
(values: ComboBoxRenderProps
& & {}
)) => ReactNode | — | The children of the component. A function may be provided to alter the children based on component state. |
className | string | (
(values: ComboBoxRenderProps
& & {}
)) => string | — | The CSS className for the element. A function may be provided to compute the class based on component state. |
style | CSSProperties | (
(values: ComboBoxRenderProps
& & {}
)) => CSSProperties | undefined | — | The inline style for the element. A function may be provided to compute the style based on component state. |
Events
Name | Type | Description |
onOpenChange | (
(isOpen: boolean,
, menuTrigger?: MenuTriggerAction
)) => void | Method that is called when the open state of the menu changes. Returns the new open state and the action that caused the opening of the menu. |
onSelectionChange | (
(key: Key
| | null
)) => void | Handler that is called when the selection changes. |
onInputChange | (
(value: string
)) => void | Handler that is called when the ComboBox input value changes. |
onFocus | (
(e: FocusEvent<HTMLInputElement>
)) => void | Handler that is called when the element receives focus. |
onBlur | (
(e: FocusEvent<HTMLInputElement>
)) => void | Handler that is called when the element loses focus. |
onFocusChange | (
(isFocused: boolean
)) => void | Handler that is called when the element's focus status changes. |
onKeyDown | (
(e: KeyboardEvent
)) => void | Handler that is called when a key is pressed. |
onKeyUp | (
(e: KeyboardEvent
)) => void | Handler that is called when a key is released. |
Layout
Name | Type | Description |
slot | string | null | A slot name for the component. Slots allow the component to receive props from a parent component.
An explicit |
Accessibility
Name | Type | Description |
id | string | The element's unique identifier. See MDN. |
aria-label | string | Defines a string value that labels the current element. |
aria-labelledby | string | Identifies the element (or elements) that labels the current element. |
aria-describedby | string | Identifies the element (or elements) that describes the object. |
aria-details | string | Identifies the element (or elements) that provide a detailed, extended description for the object. |
Label#
A <Label>
accepts all props supported by the <label>
HTML element.
Input#
An <Input>
accepts all props supported by the <input>
HTML element.
Button#
A <Button>
accepts its contents as children
. Other props such as onPress
and isDisabled
will be set by the ComboBox
.
Show props
Name | Type | Default | Description |
form | string | — | The |
formAction | string | — | The URL that processes the information submitted by the button. Overrides the action attribute of the button's form owner. |
formEncType | string | — | Indicates how to encode the form data that is submitted. |
formMethod | string | — | Indicates the HTTP method used to submit the form. |
formNoValidate | boolean | — | Indicates that the form is not to be validated when it is submitted. |
formTarget | string | — | Overrides the target attribute of the button's form owner. |
name | string | — | Submitted as a pair with the button's value as part of the form data. |
value | string | — | The value associated with the button's name when it's submitted with the form data. |
isPending | boolean | — | Whether the button is in a pending state. This disables press and hover events while retaining focusability, and announces the pending state to screen readers. |
isDisabled | boolean | — | Whether the button is disabled. |
autoFocus | boolean | — | Whether the element should receive focus on render. |
type | 'button'
| 'submit'
| 'reset' | 'button' | The behavior of the button when used in an HTML form. |
children | ReactNode | (
(values: ButtonRenderProps
& & {}
)) => ReactNode | — | The children of the component. A function may be provided to alter the children based on component state. |
className | string | (
(values: ButtonRenderProps
& & {}
)) => string | — | The CSS className for the element. A function may be provided to compute the class based on component state. |
style | CSSProperties | (
(values: ButtonRenderProps
& & {}
)) => CSSProperties | undefined | — | The inline style for the element. A function may be provided to compute the style based on component state. |
Events
Name | Type | Description |
onPress | (
(e: PressEvent
)) => void | Handler that is called when the press is released over the target. |
onPressStart | (
(e: PressEvent
)) => void | Handler that is called when a press interaction starts. |
onPressEnd | (
(e: PressEvent
)) => void | Handler that is called when a press interaction ends, either over the target or when the pointer leaves the target. |
onPressChange | (
(isPressed: boolean
)) => void | Handler that is called when the press state changes. |
onPressUp | (
(e: PressEvent
)) => void | Handler that is called when a press is released over the target, regardless of whether it started on the target or not. |
onFocus | (
(e: FocusEvent<Target>
)) => void | Handler that is called when the element receives focus. |
onBlur | (
(e: FocusEvent<Target>
)) => void | Handler that is called when the element loses focus. |
onFocusChange | (
(isFocused: boolean
)) => void | Handler that is called when the element's focus status changes. |
onKeyDown | (
(e: KeyboardEvent
)) => void | Handler that is called when a key is pressed. |
onKeyUp | (
(e: KeyboardEvent
)) => void | Handler that is called when a key is released. |
onHoverStart | (
(e: HoverEvent
)) => void | Handler that is called when a hover interaction starts. |
onHoverEnd | (
(e: HoverEvent
)) => void | Handler that is called when a hover interaction ends. |
onHoverChange | (
(isHovering: boolean
)) => void | Handler that is called when the hover state changes. |
Layout
Name | Type | Description |
slot | string | null | A slot name for the component. Slots allow the component to receive props from a parent component.
An explicit |
Accessibility
Name | Type | Description |
id | string | The element's unique identifier. See MDN. |
excludeFromTabOrder | boolean | Whether to exclude the element from the sequential tab order. If true, the element will not be focusable via the keyboard by tabbing. This should be avoided except in rare scenarios where an alternative means of accessing the element or its functionality via the keyboard is available. |
preventFocusOnPress | boolean | Whether to prevent focus from moving to the button when pressing it. Caution, this can make the button inaccessible and should only be used when alternative keyboard interaction is provided, such as ComboBox's MenuTrigger or a NumberField's increment/decrement control. |
aria-expanded | boolean
| 'true'
| 'false' | Indicates whether the element, or another grouping element it controls, is currently expanded or collapsed. |
aria-haspopup | boolean
| 'menu'
| 'listbox'
| 'tree'
| 'grid'
| 'dialog'
| 'true'
| 'false' | Indicates the availability and type of interactive popup element, such as menu or dialog, that can be triggered by an element. |
aria-controls | string | Identifies the element (or elements) whose contents or presence are controlled by the current element. |
aria-pressed | boolean
| 'true'
| 'false'
| 'mixed' | Indicates the current "pressed" state of toggle buttons. |
aria-label | string | Defines a string value that labels the current element. |
aria-labelledby | string | Identifies the element (or elements) that labels the current element. |
aria-describedby | string | Identifies the element (or elements) that describes the object. |
aria-details | string | Identifies the element (or elements) that provide a detailed, extended description for the object. |
Popover#
A <Popover>
is a container to hold the <ListBox>
suggestions for a ComboBox. By default, it has a placement
of bottom start
within a <ComboBox>
, but this and other positioning properties may be customized.
Show props
Name | Type | Default | Description |
trigger | string | — | The name of the component that triggered the popover. This is reflected on the element
as the |
triggerRef | RefObject<Element | null> | — | The ref for the element which the popover positions itself with respect to. When used within a trigger component such as DialogTrigger, MenuTrigger, Select, etc., this is set automatically. It is only required when used standalone. |
isEntering | boolean | — | Whether the popover is currently performing an entry animation. |
isExiting | boolean | — | Whether the popover is currently performing an exit animation. |
UNSTABLE_portalContainer | Element | document.body | The container element in which the overlay portal will be placed. This may have unknown behavior depending on where it is portalled to. |
offset | number | 8 | The additional offset applied along the main axis between the element and its anchor element. |
placement | Placement | 'bottom' | The placement of the element with respect to its anchor element. |
containerPadding | number | 12 | The placement padding that should be applied between the element and its surrounding container. |
crossOffset | number | 0 | The additional offset applied along the cross axis between the element and its anchor element. |
shouldFlip | boolean | true | Whether the element should flip its orientation (e.g. top to bottom or left to right) when there is insufficient room for it to render completely. |
isNonModal | boolean | — | Whether the popover is non-modal, i.e. elements outside the popover may be interacted with by assistive technologies. Most popovers should not use this option as it may negatively impact the screen reader experience. Only use with components such as combobox, which are designed to handle this situation carefully. |
isKeyboardDismissDisabled | boolean | false | Whether pressing the escape key to close the popover should be disabled. Most popovers should not use this option. When set to true, an alternative way to close the popover with a keyboard must be provided. |
shouldCloseOnInteractOutside | (
(element: Element
)) => boolean | — | When user interacts with the argument element outside of the popover ref, return true if onClose should be called. This gives you a chance to filter out interaction with elements that should not dismiss the popover. By default, onClose will always be called on interaction outside the popover ref. |
boundaryElement | Element | document.body | Element that that serves as the positioning boundary. |
scrollRef | RefObject<Element | null> | overlayRef | A ref for the scrollable region within the overlay. |
shouldUpdatePosition | boolean | true | Whether the overlay should update its position automatically. |
arrowBoundaryOffset | number | 0 | The minimum distance the arrow's edge should be from the edge of the overlay element. |
isOpen | boolean | — | Whether the overlay is open by default (controlled). |
defaultOpen | boolean | — | Whether the overlay is open by default (uncontrolled). |
children | ReactNode | (
(values: PopoverRenderProps
& & {}
)) => ReactNode | — | The children of the component. A function may be provided to alter the children based on component state. |
className | string | (
(values: PopoverRenderProps
& & {}
)) => string | — | The CSS className for the element. A function may be provided to compute the class based on component state. |
style | CSSProperties | (
(values: PopoverRenderProps
& & {}
)) => CSSProperties | undefined | — | The inline style for the element. A function may be provided to compute the style based on component state. |
Events
Name | Type | Description |
onOpenChange | (
(isOpen: boolean
)) => void | Handler that is called when the overlay's open state changes. |
Layout
Name | Type | Description |
slot | string | null | A slot name for the component. Slots allow the component to receive props from a parent component.
An explicit |
Sizing
Name | Type | Description |
maxHeight | number | The maxHeight specified for the overlay element. By default, it will take all space up to the current viewport height. |
ListBox#
Within a <ComboBox>
, most <ListBox>
props are set automatically. The <ListBox>
defines the options to display in a ComboBox.
Show props
Name | Type | Default | Description |
selectionBehavior | SelectionBehavior | — | How multiple selection should behave in the collection. |
dragAndDropHooks | DragAndDropHooks | — | The drag and drop hooks returned by useDragAndDrop used to enable drag and drop behavior for the ListBox. |
renderEmptyState | (
(props: ListBoxRenderProps
)) => ReactNode | — | Provides content to display when there are no items in the list. |
layout | 'stack' | 'grid' | 'stack' | Whether the items are arranged in a stack or grid. |
orientation | Orientation | 'vertical' | The primary orientation of the items. Usually this is the direction that the collection scrolls. |
autoFocus | boolean | FocusStrategy | — | Whether to auto focus the listbox or an option. |
shouldFocusWrap | boolean | — | Whether focus should wrap around when the end/start is reached. |
items | Iterable<T> | — | Item objects in the collection. |
disabledKeys | Iterable<Key> | — | The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with. |
selectionMode | SelectionMode | — | The type of selection that is allowed in the collection. |
disallowEmptySelection | boolean | — | Whether the collection allows empty selection. |
selectedKeys | 'all' | Iterable<Key> | — | The currently selected keys in the collection (controlled). |
defaultSelectedKeys | 'all' | Iterable<Key> | — | The initial selected keys in the collection (uncontrolled). |
children | ReactNode | (
(item: object
)) => ReactNode | — | The contents of the collection. |
dependencies | any[] | — | Values that should invalidate the item cache when using dynamic collections. |
className | string | (
(values: ListBoxRenderProps
& & {}
)) => string | — | The CSS className for the element. A function may be provided to compute the class based on component state. |
style | CSSProperties | (
(values: ListBoxRenderProps
& & {}
)) => CSSProperties | undefined | — | The inline style for the element. A function may be provided to compute the style based on component state. |
Events
Name | Type | Description |
onAction | (
(key: Key
)) => void | Handler that is called when a user performs an action on an item. The exact user event depends on
the collection's |
onSelectionChange | (
(keys: Selection
)) => void | Handler that is called when the selection changes. |
onFocus | (
(e: FocusEvent<Target>
)) => void | Handler that is called when the element receives focus. |
onBlur | (
(e: FocusEvent<Target>
)) => void | Handler that is called when the element loses focus. |
onFocusChange | (
(isFocused: boolean
)) => void | Handler that is called when the element's focus status changes. |
onScroll | (
(e: UIEvent<Element>
)) => void | Handler that is called when a user scrolls. See MDN. |
Layout
Name | Type | Description |
slot | string | null | A slot name for the component. Slots allow the component to receive props from a parent component.
An explicit |
Accessibility
Name | Type | Description |
id | string | The element's unique identifier. See MDN. |
aria-label | string | Defines a string value that labels the current element. |
aria-labelledby | string | Identifies the element (or elements) that labels the current element. |
aria-describedby | string | Identifies the element (or elements) that describes the object. |
aria-details | string | Identifies the element (or elements) that provide a detailed, extended description for the object. |
ListBoxSection#
A <ListBoxSection>
defines the child items for a section within a <ListBox>
. It may also contain an optional <Header>
element. If there is no header, then an aria-label
must be provided to identify the section to assistive technologies.
Show props
Name | Type | Description |
id | Key | The unique id of the section. |
value | object | The object value that this section represents. When using dynamic collections, this is set automatically. |
children | ReactNode | (
(item: object
)) => ReactElement | Static child items or a function to render children. |
dependencies | any[] | Values that should invalidate the item cache when using dynamic collections. |
items | Iterable<object> | Item objects in the section. |
className | string | The CSS className for the element. |
style | CSSProperties | The inline style for the element. |
Accessibility
Name | Type | Description |
aria-label | string | An accessibility label for the section. |
Header#
A <Header>
defines the title for a <ListBoxSection>
. It accepts all DOM attributes.
ListBoxItem#
A <ListBoxItem>
defines a single option within a <ListBox>
. If the children
are not plain text, then the textValue
prop must also be set to a plain text representation, which will be used for autocomplete in the ComboBox.
Show props
Name | Type | Description |
id | Key | The unique id of the item. |
value | object | The object value that this item represents. When using dynamic collections, this is set automatically. |
textValue | string | A string representation of the item's contents, used for features like typeahead. |
isDisabled | boolean | Whether the item is disabled. |
children | ReactNode | (
(values: ListBoxItemRenderProps
& & {}
)) => ReactNode | The children of the component. A function may be provided to alter the children based on component state. |
className | string | (
(values: ListBoxItemRenderProps
& & {}
)) => string | The CSS className for the element. A function may be provided to compute the class based on component state. |
style | CSSProperties | (
(values: ListBoxItemRenderProps
& & {}
)) => CSSProperties | undefined | The inline style for the element. A function may be provided to compute the style based on component state. |
href | Href | A URL to link to. See MDN. |
hrefLang | string | Hints at the human language of the linked URL. SeeMDN. |
target | HTMLAttributeAnchorTarget | The target window for the link. See MDN. |
rel | string | The relationship between the linked resource and the current page. See MDN. |
download | boolean | string | Causes the browser to download the linked URL. A string may be provided to suggest a file name. See MDN. |
ping | string | A space-separated list of URLs to ping when the link is followed. See MDN. |
referrerPolicy | HTMLAttributeReferrerPolicy | How much of the referrer to send when following the link. See MDN. |
routerOptions | RouterOptions | Options for the configured client side router. |
Events
Name | Type | Description |
onAction | () => void | Handler that is called when a user performs an action on the item. The exact user event depends on
the collection's |
onHoverStart | (
(e: HoverEvent
)) => void | Handler that is called when a hover interaction starts. |
onHoverEnd | (
(e: HoverEvent
)) => void | Handler that is called when a hover interaction ends. |
onHoverChange | (
(isHovering: boolean
)) => void | Handler that is called when the hover state changes. |
Accessibility
Name | Type | Description |
aria-label | string | An accessibility label for this item. |
FieldError#
A <FieldError>
displays validation errors.
Show props
Name | Type | Description |
children | ReactNode | (
(values: FieldErrorRenderProps
& & {}
)) => ReactNode | The children of the component. A function may be provided to alter the children based on component state. |
className | string | (
(values: FieldErrorRenderProps
& & {}
)) => string | The CSS className for the element. A function may be provided to compute the class based on component state. |
style | CSSProperties | (
(values: FieldErrorRenderProps
& & {}
)) => CSSProperties | undefined | The inline style for the element. A function may be provided to compute the style based on component state. |
Styling#
React Aria components can be styled in many ways, including using CSS classes, inline styles, utility classes (e.g. Tailwind), CSS-in-JS (e.g. Styled Components), etc. By default, all components include a builtin className
attribute which can be targeted using CSS selectors. These follow the react-aria-ComponentName
naming convention.
.react-aria-ComboBox {
/* ... */
}
.react-aria-ComboBox {
/* ... */
}
.react-aria-ComboBox {
/* ... */
}
A custom className
can also be specified on any component. This overrides the default className
provided by React Aria with your own.
<ComboBox className="my-combobox">
{/* ... */}
</ComboBox>
<ComboBox className="my-combobox">
{/* ... */}
</ComboBox>
<ComboBox className="my-combobox">
{/* ... */}
</ComboBox>
In addition, some components support multiple UI states (e.g. pressed, hovered, etc.). React Aria components expose states using data attributes, which you can target in CSS selectors. For example:
.react-aria-ListBoxItem[data-selected] {
/* ... */
}
.react-aria-ListBoxItem[data-focused] {
/* ... */
}
.react-aria-ListBoxItem[data-selected] {
/* ... */
}
.react-aria-ListBoxItem[data-focused] {
/* ... */
}
.react-aria-ListBoxItem[data-selected] {
/* ... */
}
.react-aria-ListBoxItem[data-focused] {
/* ... */
}
The className
and style
props also accept functions which receive states for styling. This lets you dynamically determine the classes or styles to apply, which is useful when using utility CSS libraries like Tailwind.
<ListBoxItem
className={({ isSelected }) => isSelected ? 'bg-blue-400' : 'bg-gray-100'}
>
Item
</ListBoxItem>
<ListBoxItem
className={({ isSelected }) =>
isSelected ? 'bg-blue-400' : 'bg-gray-100'}
>
Item
</ListBoxItem>
<ListBoxItem
className={(
{ isSelected }
) =>
isSelected
? 'bg-blue-400'
: 'bg-gray-100'}
>
Item
</ListBoxItem>
Render props may also be used as children to alter what elements are rendered based on the current state. For example, you could render a checkmark icon when an item is selected.
<ListBoxItem>
{({isSelected}) => (
<>
{isSelected && <CheckmarkIcon />}
Item
</>
)}
</ListBoxItem>
<ListBoxItem>
{({isSelected}) => (
<>
{isSelected && <CheckmarkIcon />}
Item
</>
)}
</ListBoxItem>
<ListBoxItem>
{(
{ isSelected }
) => (
<>
{isSelected && (
<CheckmarkIcon />
)}
Item
</>
)}
</ListBoxItem>
The states and selectors for each component used in a ComboBox
are documented below.
ComboBox#
A ComboBox
can be targeted with the .react-aria-ComboBox
CSS selector, or by overriding with a custom className
. It supports the following states:
Name | CSS Selector | Description |
isOpen | [data-open] | Whether the combobox is currently open. |
isDisabled | [data-disabled] | Whether the combobox is disabled. |
isInvalid | [data-invalid] | Whether the combobox is invalid. |
isRequired | [data-required] | Whether the combobox is required. |
Label#
A Label
can be targeted with the .react-aria-Label
CSS selector, or by overriding with a custom className
.
Input#
An Input
can be targeted with the .react-aria-Input
CSS selector, or by overriding with a custom className
. It supports the following states:
Name | CSS Selector | Description |
isHovered | [data-hovered] | Whether the input is currently hovered with a mouse. |
isFocused | [data-focused] | Whether the input is focused, either via a mouse or keyboard. |
isFocusVisible | [data-focus-visible] | Whether the input is keyboard focused. |
isDisabled | [data-disabled] | Whether the input is disabled. |
isInvalid | [data-invalid] | Whether the input is invalid. |
Button#
A Button can be targeted with the .react-aria-Button
CSS selector, or by overriding with a custom className
. It supports the following states:
Name | CSS Selector | Description |
isHovered | [data-hovered] | Whether the button is currently hovered with a mouse. |
isPressed | [data-pressed] | Whether the button is currently in a pressed state. |
isFocused | [data-focused] | Whether the button is focused, either via a mouse or keyboard. |
isFocusVisible | [data-focus-visible] | Whether the button is keyboard focused. |
isDisabled | [data-disabled] | Whether the button is disabled. |
isPending | [data-pending] | Whether the button is currently in a pending state. |
Popover#
The Popover component can be targeted with the .react-aria-Popover
CSS selector, or by overriding with a custom className
. Note that it renders in a React Portal, so it will not appear as a descendant of the ComboBox in the DOM. It supports the following states and render props:
Name | CSS Selector | Description |
trigger | [data-trigger="..."] | The name of the component that triggered the popover, e.g. "DialogTrigger" or "ComboBox". |
placement | [data-placement="left | right | top | bottom"] | The placement of the popover relative to the trigger. |
isEntering | [data-entering] | Whether the popover is currently entering. Use this to apply animations. |
isExiting | [data-exiting] | Whether the popover is currently exiting. Use this to apply animations. |
Within a ComboBox, the popover will have the data-trigger="ComboBox"
attribute, which can be used to define combobox-specific styles. In addition, the --trigger-width
CSS custom property will be set on the popover, which you can use to make the popover match the width of the combobox.
.react-aria-Popover[data-trigger=ComboBox] {
width: var(--trigger-width);
}
.react-aria-Popover[data-trigger=ComboBox] {
width: var(--trigger-width);
}
.react-aria-Popover[data-trigger=ComboBox] {
width: var(--trigger-width);
}
ListBox#
A ListBox can be targeted with the .react-aria-ListBox
CSS selector, or by overriding with a custom className
.
ListBoxSection#
A ListBoxSection
can be targeted with the .react-aria-ListBoxSection
CSS selector, or by overriding with a custom className
. See sections for examples.
Header#
A Header
within a ListBoxSection
can be targeted with the .react-aria-Header
CSS selector, or by overriding with a custom className
. See sections for examples.
ListBoxItem#
A ListBoxItem
can be targeted with the .react-aria-ListBoxItem
CSS selector, or by overriding with a custom className
. It supports the following states and render props:
Name | CSS Selector | Description |
isHovered | [data-hovered] | Whether the item is currently hovered with a mouse. |
isPressed | [data-pressed] | Whether the item is currently in a pressed state. |
isSelected | [data-selected] | Whether the item is currently selected. |
isFocused | [data-focused] | Whether the item is currently focused. |
isFocusVisible | [data-focus-visible] | Whether the item is currently keyboard focused. |
isDisabled | [data-disabled] | Whether the item is non-interactive, i.e. both selection and actions are disabled and the item may
not be focused. Dependent on |
selectionMode | [data-selection-mode="single | multiple"] | The type of selection that is allowed in the collection. |
selectionBehavior | — | The selection behavior for the collection. |
Items also support two slots: a label, and a description. When provided using the <Text>
element, the item will have aria-labelledby
and aria-describedby
attributes pointing to these slots, improving screen reader announcement. See complex items for an example.
Note that items may not contain interactive children such as buttons, as screen readers will not be able to access them.
Text#
The help text elements within a ComboBox
can be targeted with the [slot=description]
and [slot=errorMessage]
CSS selectors, or by adding a custom className
.
FieldError#
A FieldError
can be targeted with the .react-aria-FieldError
CSS selector, or by overriding with a custom className
. It supports the following render props:
Name | Description |
isInvalid | Whether the input value is invalid. |
validationErrors | The current error messages for the input if it is invalid, otherwise an empty array. |
validationDetails | The native validation details for the input. |
Advanced customization#
Composition#
If you need to customize one of the components within a ComboBox
, such as Input
or ListBox
, in many cases you can create a wrapper
component. This lets you customize the props passed to the component.
function MyListBox(props) {
return <ListBox {...props} className="my-listbox" />
}
function MyListBox(props) {
return <ListBox {...props} className="my-listbox" />
}
function MyListBox(
props
) {
return (
<ListBox
{...props}
className="my-listbox"
/>
);
}
Contexts#
All React Aria Components export a corresponding context that can be used to send props to them from a parent element. This enables you to build your own compositional APIs similar to those found in React Aria Components itself. You can send any prop or ref via context that you could pass to the corresponding component. The local props and ref on the component are merged with the ones passed via context, with the local props taking precedence (following the rules documented in mergeProps).
Component | Context | Props | Ref |
ComboBox | ComboBoxContext | ComboBoxProps | HTMLDivElement |
This example shows a FieldGroup
component that renders a group of filters with a title. The entire group can be marked as disabled via the isDisabled
prop, which is passed to all child selects via the ComboBoxContext
provider.
import {ComboBoxContext} from 'react-aria-components';
interface FieldGroupProps {
title?: string,
children?: React.ReactNode,
isDisabled?: boolean
}
function FieldGroup({title, children, isDisabled}: FieldGroupProps) {
return (
<fieldset>
<legend>{title}</legend>
<ComboBoxContext.Provider value={{isDisabled}}> {children}
</ComboBoxContext.Provider>
</fieldset>
);
}
<FieldGroup title="Filters" isDisabled>
<MyComboBox label="Status" defaultSelectedKey="published">
<ListBoxItem id="draft">Draft</ListBoxItem>
<ListBoxItem id="published">Published</ListBoxItem>
<ListBoxItem id="deleted">Deleted</ListBoxItem>
</MyComboBox>
<MyComboBox label="Author" defaultSelectedKey="emma">
<ListBoxItem id="john">John</ListBoxItem>
<ListBoxItem id="emma">Emma</ListBoxItem>
<ListBoxItem id="tim">Tim</ListBoxItem>
</MyComboBox>
</FieldGroup>
import {ComboBoxContext} from 'react-aria-components';
interface FieldGroupProps {
title?: string;
children?: React.ReactNode;
isDisabled?: boolean;
}
function FieldGroup(
{ title, children, isDisabled }: FieldGroupProps
) {
return (
<fieldset>
<legend>{title}</legend>
<ComboBoxContext.Provider value={{ isDisabled }}> {children}
</ComboBoxContext.Provider>
</fieldset>
);
}
<FieldGroup title="Filters" isDisabled>
<MyComboBox
label="Status"
defaultSelectedKey="published"
>
<ListBoxItem id="draft">Draft</ListBoxItem>
<ListBoxItem id="published">Published</ListBoxItem>
<ListBoxItem id="deleted">Deleted</ListBoxItem>
</MyComboBox>
<MyComboBox label="Author" defaultSelectedKey="emma">
<ListBoxItem id="john">John</ListBoxItem>
<ListBoxItem id="emma">Emma</ListBoxItem>
<ListBoxItem id="tim">Tim</ListBoxItem>
</MyComboBox>
</FieldGroup>
import {ComboBoxContext} from 'react-aria-components';
interface FieldGroupProps {
title?: string;
children?:
React.ReactNode;
isDisabled?: boolean;
}
function FieldGroup(
{
title,
children,
isDisabled
}: FieldGroupProps
) {
return (
<fieldset>
<legend>
{title}
</legend>
<ComboBoxContext.Provider
value={{
isDisabled
}}
> {children}
</ComboBoxContext.Provider>
</fieldset>
);
}
<FieldGroup
title="Filters"
isDisabled
>
<MyComboBox
label="Status"
defaultSelectedKey="published"
>
<ListBoxItem id="draft">
Draft
</ListBoxItem>
<ListBoxItem id="published">
Published
</ListBoxItem>
<ListBoxItem id="deleted">
Deleted
</ListBoxItem>
</MyComboBox>
<MyComboBox
label="Author"
defaultSelectedKey="emma"
>
<ListBoxItem id="john">
John
</ListBoxItem>
<ListBoxItem id="emma">
Emma
</ListBoxItem>
<ListBoxItem id="tim">
Tim
</ListBoxItem>
</MyComboBox>
</FieldGroup>
Show CSS
fieldset {
padding: 1.5em;
width: fit-content;
}
fieldset {
padding: 1.5em;
width: fit-content;
}
fieldset {
padding: 1.5em;
width: fit-content;
}
Custom children#
ComboBox passes props to its child components, such as the label and input, via their associated contexts. These contexts are exported so you can also consume them in your own custom components. This enables you to reuse existing components from your app or component library together with React Aria Components.
Component | Context | Props | Ref |
Label | LabelContext | LabelProps | HTMLLabelElement |
Button | ButtonContext | ButtonProps | HTMLButtonElement |
Input | InputContext | InputProps | HTMLInputElement |
Popover | PopoverContext | PopoverProps | HTMLElement |
ListBox | ListBoxContext | ListBoxProps | HTMLDivElement |
Text | TextContext | TextProps | HTMLElement |
This example consumes from LabelContext
in an existing styled label component to make it compatible with React Aria Components. The useContextProps
hook merges the local props and ref with the ones provided via context by ComboBox.
import type {LabelProps} from 'react-aria-components';
import {LabelContext, useContextProps} from 'react-aria-components';
const MyCustomLabel = React.forwardRef(
(props: LabelProps, ref: React.ForwardedRef<HTMLLabelElement>) => {
// Merge the local props and ref with the ones provided via context.
[props, ref] = useContextProps(props, ref, LabelContext);
// ... your existing Label component
return <label {...props} ref={ref} />;
}
);
import type {LabelProps} from 'react-aria-components';
import {
LabelContext,
useContextProps
} from 'react-aria-components';
const MyCustomLabel = React.forwardRef(
(
props: LabelProps,
ref: React.ForwardedRef<HTMLLabelElement>
) => {
// Merge the local props and ref with the ones provided via context.
[props, ref] = useContextProps(
props,
ref,
LabelContext
);
// ... your existing Label component
return <label {...props} ref={ref} />;
}
);
import type {LabelProps} from 'react-aria-components';
import {
LabelContext,
useContextProps
} from 'react-aria-components';
const MyCustomLabel =
React.forwardRef(
(
props: LabelProps,
ref:
React.ForwardedRef<
HTMLLabelElement
>
) => {
// Merge the local props and ref with the ones provided via context.
[props, ref] =
useContextProps(
props,
ref,
LabelContext
);
// ... your existing Label component
return (
<label
{...props}
ref={ref}
/>
);
}
);
Now you can use MyCustomLabel
within a ComboBox
, in place of the builtin React Aria Components Label
.
<ComboBox>
<MyCustomLabel>Name</MyCustomLabel> {/* ... */}
</ComboBox>
<ComboBox>
<MyCustomLabel>Name</MyCustomLabel> {/* ... */}
</ComboBox>
<ComboBox>
<MyCustomLabel>
Name
</MyCustomLabel> {/* ... */}
</ComboBox>
State#
ComboBox provides an ComboBoxState
object to its children via ComboBoxStateContext
. This can be used to access and manipulate the combo box's state.
This example shows a ComboBoxClearButton
component that can be placed within a ComboBox
to allow the user to clear the selected value.
import {ComboBoxStateContext} from 'react-aria-components';
function ComboBoxClearButton() {
let state = React.useContext(ComboBoxStateContext); return (
<Button
// Don't inherit default Button behavior from ComboBox.
slot={null}
className="clear-button"
aria-label="Clear"
onPress={() => state?.setSelectedKey(null)}>
✕
</Button>
);
}
<ComboBox defaultSelectedKey="cat">
<Label>Favorite Animal</Label>
<div>
<Input />
<ComboBoxClearButton /> <Button>▼</Button>
</div>
<Popover>
<ListBox>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
<ListBoxItem id="kangaroo">Kangaroo</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
import {ComboBoxStateContext} from 'react-aria-components';
function ComboBoxClearButton() {
let state = React.useContext(ComboBoxStateContext); return (
<Button
// Don't inherit default Button behavior from ComboBox.
slot={null}
className="clear-button"
aria-label="Clear"
onPress={() => state?.setSelectedKey(null)}
>
✕
</Button>
);
}
<ComboBox defaultSelectedKey="cat">
<Label>Favorite Animal</Label>
<div>
<Input />
<ComboBoxClearButton /> <Button>▼</Button>
</div>
<Popover>
<ListBox>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
<ListBoxItem id="kangaroo">Kangaroo</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
import {ComboBoxStateContext} from 'react-aria-components';
function ComboBoxClearButton() {
let state = React
.useContext(
ComboBoxStateContext
); return (
<Button
// Don't inherit default Button behavior from ComboBox.
slot={null}
className="clear-button"
aria-label="Clear"
onPress={() =>
state
?.setSelectedKey(
null
)}
>
✕
</Button>
);
}
<ComboBox defaultSelectedKey="cat">
<Label>
Favorite Animal
</Label>
<div>
<Input />
<ComboBoxClearButton /> <Button>▼</Button>
</div>
<Popover>
<ListBox>
<ListBoxItem id="cat">
Cat
</ListBoxItem>
<ListBoxItem id="dog">
Dog
</ListBoxItem>
<ListBoxItem id="kangaroo">
Kangaroo
</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
Show CSS
.clear-button {
width: 1.143rem;
height: 1.143rem;
border-radius: 1.143rem;
margin-left: -3.143rem;
font-size: 0.857rem;
line-height: 0.857rem;
vertical-align: middle;
text-align: center;
background: gray;
color: white;
border: none;
padding: 0;
outline: none;
&[data-pressed] {
background: dimgray;
}
&[data-focus-visible] {
outline: 2px solid var(--focus-ring-color);
outline-offset: 2px;
}
+ .react-aria-Button {
margin-left: 4px;
}
}
.clear-button {
width: 1.143rem;
height: 1.143rem;
border-radius: 1.143rem;
margin-left: -3.143rem;
font-size: 0.857rem;
line-height: 0.857rem;
vertical-align: middle;
text-align: center;
background: gray;
color: white;
border: none;
padding: 0;
outline: none;
&[data-pressed] {
background: dimgray;
}
&[data-focus-visible] {
outline: 2px solid var(--focus-ring-color);
outline-offset: 2px;
}
+ .react-aria-Button {
margin-left: 4px;
}
}
.clear-button {
width: 1.143rem;
height: 1.143rem;
border-radius: 1.143rem;
margin-left: -3.143rem;
font-size: 0.857rem;
line-height: 0.857rem;
vertical-align: middle;
text-align: center;
background: gray;
color: white;
border: none;
padding: 0;
outline: none;
&[data-pressed] {
background: dimgray;
}
&[data-focus-visible] {
outline: 2px solid var(--focus-ring-color);
outline-offset: 2px;
}
+ .react-aria-Button {
margin-left: 4px;
}
}
Hooks#
If you need to customize things even further, such as accessing internal state, intercepting events, or customizing the DOM structure, you can drop down to the lower level Hook-based API. See useComboBox for more details.