Select
A select displays a collapsible list of options and allows a user to select one of them.
install | yarn add react-aria-components |
---|---|
version | 3.17.0 |
usage | import {Select} from 'react-aria-components' |
Example#
import {Button, Item, Label, ListBox, Popover, Select, SelectValue} from 'react-aria-components';
<Select>
<Label>Favorite Animal</Label>
<Button>
<SelectValue />
<span aria-hidden="true">▼</span>
</Button>
<Popover>
<ListBox>
<Item>Aardvark</Item>
<Item>Cat</Item>
<Item>Dog</Item>
<Item>Kangaroo</Item>
<Item>Panda</Item>
<Item>Snake</Item>
</ListBox>
</Popover>
</Select>
import {
Button,
Item,
Label,
ListBox,
Popover,
Select,
SelectValue
} from 'react-aria-components';
<Select>
<Label>Favorite Animal</Label>
<Button>
<SelectValue />
<span aria-hidden="true">▼</span>
</Button>
<Popover>
<ListBox>
<Item>Aardvark</Item>
<Item>Cat</Item>
<Item>Dog</Item>
<Item>Kangaroo</Item>
<Item>Panda</Item>
<Item>Snake</Item>
</ListBox>
</Popover>
</Select>
import {
Button,
Item,
Label,
ListBox,
Popover,
Select,
SelectValue
} from 'react-aria-components';
<Select>
<Label>
Favorite Animal
</Label>
<Button>
<SelectValue />
<span aria-hidden="true">
▼
</span>
</Button>
<Popover>
<ListBox>
<Item>
Aardvark
</Item>
<Item>Cat</Item>
<Item>Dog</Item>
<Item>
Kangaroo
</Item>
<Item>
Panda
</Item>
<Item>
Snake
</Item>
</ListBox>
</Popover>
</Select>
Show CSS
.react-aria-Select {
& button {
background: var(--spectrum-global-color-gray-50);
border: 1px solid var(--spectrum-global-color-gray-400);
box-shadow: 0 1px 2px rgba(0 0 0 / 0.1);
border-radius: 6px;
appearance: none;
vertical-align: middle;
font-size: 1.072rem;
padding: 4px 4px 4px 8px;
margin: 0;
outline: none;
display: flex;
align-items: center;
max-width: 250px;
&[data-focus-visible] {
border-color: slateblue;
box-shadow: 0 0 0 1px slateblue;
}
&[data-pressed] {
background: var(--spectrum-global-color-gray-150);
}
&:disabled {
opacity: 0.5;
}
}
& .react-aria-SelectValue {
&[data-placeholder] {
font-style: italic;
color: var(--spectrum-global-color-gray-700);
}
& [slot=description] {
display: none;
}
}
& span[aria-hidden] {
width: 1.5rem;
line-height: 1.375rem;
margin-left: 1rem;
padding: 1px;
background: slateblue;
color: white;
border-radius: 4px;
font-size: 0.857rem;
}
& [slot=description] {
font-size: 12px;
}
& [slot=errorMessage] {
font-size: 12px;
color: var(--spectrum-global-color-red-600);
}
}
.react-aria-ListBox {
max-height: inherit;
overflow: auto;
padding: 2px;
border: 1px solid var(--spectrum-global-color-gray-400);
box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
border-radius: 6px;
background: var(--page-background);
outline: none;
min-width: var(--button-width);
max-width: 250px;
box-sizing: border-box;
& section:not(:first-child) {
margin-top: 12px;
}
& section header {
font-size: 16px;
font-weight: bold;
padding: 0 8px 0 22px;
}
& .react-aria-Item {
margin: 2px;
padding: 4px 8px 4px 22px;
border-radius: 6px;
outline: none;
cursor: default;
color: var(--spectrum-global-color-gray-800);
font-size: 1.072rem;
position: relative;
display: flex;
flex-direction: column;
&[aria-selected=true] {
font-weight: 600;
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&[data-focused],
&[data-pressed] {
background: slateblue;
color: white;
}
&[aria-disabled] {
opacity: 0.4;
}
& [slot=label] {
font-weight: bold;
}
& [slot=description] {
font-size: small;
}
}
}
.react-aria-Select {
& button {
background: var(--spectrum-global-color-gray-50);
border: 1px solid var(--spectrum-global-color-gray-400);
box-shadow: 0 1px 2px rgba(0 0 0 / 0.1);
border-radius: 6px;
appearance: none;
vertical-align: middle;
font-size: 1.072rem;
padding: 4px 4px 4px 8px;
margin: 0;
outline: none;
display: flex;
align-items: center;
max-width: 250px;
&[data-focus-visible] {
border-color: slateblue;
box-shadow: 0 0 0 1px slateblue;
}
&[data-pressed] {
background: var(--spectrum-global-color-gray-150);
}
&:disabled {
opacity: 0.5;
}
}
& .react-aria-SelectValue {
&[data-placeholder] {
font-style: italic;
color: var(--spectrum-global-color-gray-700);
}
& [slot=description] {
display: none;
}
}
& span[aria-hidden] {
width: 1.5rem;
line-height: 1.375rem;
margin-left: 1rem;
padding: 1px;
background: slateblue;
color: white;
border-radius: 4px;
font-size: 0.857rem;
}
& [slot=description] {
font-size: 12px;
}
& [slot=errorMessage] {
font-size: 12px;
color: var(--spectrum-global-color-red-600);
}
}
.react-aria-ListBox {
max-height: inherit;
overflow: auto;
padding: 2px;
border: 1px solid var(--spectrum-global-color-gray-400);
box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
border-radius: 6px;
background: var(--page-background);
outline: none;
min-width: var(--button-width);
max-width: 250px;
box-sizing: border-box;
& section:not(:first-child) {
margin-top: 12px;
}
& section header {
font-size: 16px;
font-weight: bold;
padding: 0 8px 0 22px;
}
& .react-aria-Item {
margin: 2px;
padding: 4px 8px 4px 22px;
border-radius: 6px;
outline: none;
cursor: default;
color: var(--spectrum-global-color-gray-800);
font-size: 1.072rem;
position: relative;
display: flex;
flex-direction: column;
&[aria-selected=true] {
font-weight: 600;
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&[data-focused],
&[data-pressed] {
background: slateblue;
color: white;
}
&[aria-disabled] {
opacity: 0.4;
}
& [slot=label] {
font-weight: bold;
}
& [slot=description] {
font-size: small;
}
}
}
.react-aria-Select {
& button {
background: var(--spectrum-global-color-gray-50);
border: 1px solid var(--spectrum-global-color-gray-400);
box-shadow: 0 1px 2px rgba(0 0 0 / 0.1);
border-radius: 6px;
appearance: none;
vertical-align: middle;
font-size: 1.072rem;
padding: 4px 4px 4px 8px;
margin: 0;
outline: none;
display: flex;
align-items: center;
max-width: 250px;
&[data-focus-visible] {
border-color: slateblue;
box-shadow: 0 0 0 1px slateblue;
}
&[data-pressed] {
background: var(--spectrum-global-color-gray-150);
}
&:disabled {
opacity: 0.5;
}
}
& .react-aria-SelectValue {
&[data-placeholder] {
font-style: italic;
color: var(--spectrum-global-color-gray-700);
}
& [slot=description] {
display: none;
}
}
& span[aria-hidden] {
width: 1.5rem;
line-height: 1.375rem;
margin-left: 1rem;
padding: 1px;
background: slateblue;
color: white;
border-radius: 4px;
font-size: 0.857rem;
}
& [slot=description] {
font-size: 12px;
}
& [slot=errorMessage] {
font-size: 12px;
color: var(--spectrum-global-color-red-600);
}
}
.react-aria-ListBox {
max-height: inherit;
overflow: auto;
padding: 2px;
border: 1px solid var(--spectrum-global-color-gray-400);
box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
border-radius: 6px;
background: var(--page-background);
outline: none;
min-width: var(--button-width);
max-width: 250px;
box-sizing: border-box;
& section:not(:first-child) {
margin-top: 12px;
}
& section header {
font-size: 16px;
font-weight: bold;
padding: 0 8px 0 22px;
}
& .react-aria-Item {
margin: 2px;
padding: 4px 8px 4px 22px;
border-radius: 6px;
outline: none;
cursor: default;
color: var(--spectrum-global-color-gray-800);
font-size: 1.072rem;
position: relative;
display: flex;
flex-direction: column;
&[aria-selected=true] {
font-weight: 600;
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&[data-focused],
&[data-pressed] {
background: slateblue;
color: white;
}
&[aria-disabled] {
opacity: 0.4;
}
& [slot=label] {
font-weight: bold;
}
& [slot=description] {
font-size: small;
}
}
}
Features#
A select can be built using the <select>
and <option> HTML elements, but this is
not possible to style consistently cross browser, especially the options. Select
helps achieve accessible
select components that can be styled as needed without compromising on high quality interactions.
- Flexible – Support for controlled and uncontrolled state, async loading, disabled items, validation, sections, complex items, and more.
- Keyboard navigation – Select can be opened and navigated using the arrow keys, along with page up/down, home/end, etc. Auto scrolling, and typeahead both in the listbox and on the button, are supported as well.
- Accessible – Follows the ARIA listbox pattern, with support for items and sections, and slots for label and description elements within each item for improved screen reader announcement.
- HTML form integration – A visually hidden
<select>
element is included to enable HTML form integration, autofill, and mobile form navigation via the software keyboard. - Styleable – Items include builtin states for styling, such as hover, press, focus, selected, and disabled.
Anatomy#
A select consists of a label, a button which displays a selected value, and a listbox, displayed in a popup. Users can click, touch, or use the keyboard on the button to open the listbox popup.
Select
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.
If a select does not have a visible label, an aria-label
or aria-labelledby
prop must be passed instead to identify it to assistive technology.
Concepts#
Select
makes use of the following concepts:
Composed components#
A Select
uses the following components, which may also be used standalone or reused in other components.
Props#
Select#
Name | Type | Default | Description |
autoComplete | string | — | Describes the type of autocomplete functionality the input should provide if any. See MDN. |
name | string | — | The name of the input, used when submitting an HTML form. |
isOpen | boolean | — | Sets the open state of the menu. |
defaultOpen | boolean | — | Sets the default open state of the menu. |
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. |
isDisabled | boolean | — | Whether the input is disabled. |
validationState | ValidationState | — | Whether the input should display its "valid" or "invalid" visual styling. |
isRequired | boolean | — | Whether user input is required on the input before form submission.
Often paired with the |
placeholder | string | — | Temporary text that occupies the text input when it is empty. |
selectedKey | Key | null | — | The currently selected key in the collection (controlled). |
defaultSelectedKey | Key | — | The initial selected key in the collection (uncontrolled). |
autoFocus | boolean | — | Whether the element should receive focus on render. |
children | ReactNode | (
(values: SelectState<object>
)) => ReactNode | — | |
className | string | (
(values: SelectState<object>
)) => string | — | |
style | CSSProperties | (
(values: SelectState<object>
)) => CSSProperties | — |
Events
Name | Type | Default | Description |
onOpenChange | (
(isOpen: boolean
)) => void | — | Method that is called when the open state of the menu changes. |
onSelectionChange | (
(key: Key
)) => any | — | Handler that is called when the selection changes. |
onFocus | (
(e: FocusEvent
)) => void | — | Handler that is called when the element receives focus. |
onBlur | (
(e: FocusEvent
)) => 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. |
Accessibility
Name | Type | Default | 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. |
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 HTML attributes.
Button#
A <Button>
accepts its contents as children
. Other props such as onPress
and isDisabled
will be set by the Select
.
Show props
Name | Type | Default | Description |
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 | — | |
className | string | (
(values: ButtonRenderProps
)) => string | — | |
style | CSSProperties | (
(values: ButtonRenderProps
)) => CSSProperties | — |
Events
Name | Type | Default | 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
)) => void | — | Handler that is called when the element receives focus. |
onBlur | (
(e: FocusEvent
)) => 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 | Default | Description |
slot | string | — |
Accessibility
Name | Type | Default | 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. |
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. |
SelectValue#
The <SelectValue>
component displays the current value of the select within the <Button>
, or a placeholder if no value is selected.
Show props
Name | Type | Default | Description |
children | ReactNode | (
(values: SelectValueRenderProps
)) => ReactNode | — | |
className | string | (
(values: SelectValueRenderProps
)) => string | — | |
style | CSSProperties | (
(values: SelectValueRenderProps
)) => CSSProperties | — |
Popover#
A <Popover>
is a container to hold the <ListBox>
suggestions for a Select. By default, it has a placement
of bottom start
within a <Select>
, but this and other positioning properties may be customized.
Show props
Name | Type | Default | Description |
triggerRef | RefObject<Element> | — | 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. |
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. |
offset | number | 0 | The additional offset applied along the main axis between the element and its anchor element. |
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. |
children | ReactNode | (
(values: PopoverRenderProps
)) => ReactNode | — | |
className | string | (
(values: PopoverRenderProps
)) => string | — | |
style | CSSProperties | (
(values: PopoverRenderProps
)) => CSSProperties | — |
ListBox#
Within a <Select>
, most <ListBox>
props are set automatically. The <ListBox>
defines the options to display in a Select.
Show props
Name | Type | Default | Description |
selectionBehavior | SelectionBehavior | — | How multiple selection should behave in the collection. |
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: T
)) => ReactElement | — | |
className | string | — | |
style | CSSProperties | — |
Events
Name | Type | Default | Description |
onSelectionChange | (
(keys: Selection
)) => any | — | Handler that is called when the selection changes. |
onFocus | (
(e: FocusEvent
)) => void | — | Handler that is called when the element receives focus. |
onBlur | (
(e: FocusEvent
)) => 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. |
Accessibility
Name | Type | Default | 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. |
Section#
A <Section>
defines the child items, and optional title for a section within a <ListBox>
.
Show props
Item#
An <Item>
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 Select.
Show props
Name | Type | Default | Description |
title | ReactNode | — | Rendered contents of the item if children contains child items. |
textValue | string | — | A string representation of the item's contents, used for features like typeahead. |
childItems | Iterable<T> | — | A list of child item objects. Used for dynamic collections. |
hasChildItems | boolean | — | Whether this item has children, even if not loaded yet. |
children | ReactNode | (
(values: ItemStates
)) => ReactNode | — | |
className | string | (
(values: ItemStates
)) => string | — | |
style | CSSProperties | (
(values: ItemStates
)) => CSSProperties | — |
Accessibility
Name | Type | Default | Description |
aria-label | string | — | An accessibility label for this item. |
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-Select {
/* ... */
}
.react-aria-Select {
/* ... */
}
.react-aria-Select {
/* ... */
}
A custom className
can also be specified on any component. This overrides the default className
provided by React Aria with your own.
<Select className="my-select">
{/* ... */}
</Select>
<Select className="my-select">
{/* ... */}
</Select>
<Select className="my-select">
{/* ... */}
</Select>;
In addition, some components support multiple UI states (e.g. pressed, hovered, etc.). React Aria components expose states using DOM attributes, which you can target in CSS selectors. These are ARIA attributes wherever possible, or data attributes when a relevant ARIA attribute does not exist. For example:
.react-aria-Item[aria-selected=true] {
/* ... */
}
.react-aria-Item[data-focused] {
/* ... */
}
.react-aria-Item[aria-selected=true] {
/* ... */
}
.react-aria-Item[data-focused] {
/* ... */
}
.react-aria-Item[aria-selected=true] {
/* ... */
}
.react-aria-Item[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.
<Item className={({isSelected}) => isSelected ? 'bg-blue-400' : 'bg-gray-100'}>
Item
</Item>
<Item
className={({ isSelected }) =>
isSelected ? 'bg-blue-400' : 'bg-gray-100'}
>
Item
</Item>;
<Item
className={(
{ isSelected }
) =>
isSelected
? 'bg-blue-400'
: 'bg-gray-100'}
>
Item
</Item>;
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.
<Item>
{({isSelected}) => (
<>
{isSelected && <CheckmarkIcon />}
Item
</>
)}
</Item>
<Item>
{({isSelected}) => (
<>
{isSelected && <CheckmarkIcon />}
Item
</>
)}
</Item>
<Item>
{({ isSelected }) => (
<>
{isSelected && (
<CheckmarkIcon />
)}
Item
</>
)}
</Item>;
The states and selectors for each component used in a Select
are documented below.
Select#
A Select
can be targeted with the .react-aria-Select
CSS selector, or by overriding with a custom className
. It provides a SelectState
object to its render props, which can be used to customize the className
, style
, or children
.
Label#
A Label
can be targeted with the .react-aria-Label
CSS selector, or by overriding with a custom className
.
Button#
A Button can be targeted with the button
CSS selector, or by adding 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 | :focus | Whether the button is focused, either via a mouse or keyboard. |
isFocusVisible | [data-focus-visible] | Whether the button is keyboard focused. |
isDisabled | :disabled | Whether the button is disabled. |
SelectValue#
A SelectValue
can be targed with the .react-aria-SelectValue
CSS selector, or by overriding with a custom className
. It supports the following states and render props:
Name | CSS Selector | Description |
isPlaceholder | [data-placeholder] | Whether the value is a placeholder. |
selectedItem | — | The rendered value of the currently selected item. |
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 Select in the DOM.
The --button-width
CSS custom property will be set on the popover, which you can use to make the popover match the width of the select button.
.react-aria-Popover {
width: var(--button-width);
}
.react-aria-Popover {
width: var(--button-width);
}
.react-aria-Popover {
width: var(--button-width);
}
ListBox#
A ListBox
can be targeted with the .react-aria-ListBox
CSS selector, or by overriding with a custom className
.
Section#
A Section
can be targeted with the .react-aria-Section
CSS selector, or by overriding with a custom className
. The section title can be targeted with the header
selector. The title
prop also allows JSX elements and not just strings, which can enable custom formatting. However, keep in mind that interactive elements within a listbox are not allowed. See sections for examples.
Item#
An Item
can be targeted with the .react-aria-Item
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 | [aria-selected=true] | 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 | [aria-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 | — | 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 Select
can be targeted with the [slot=description]
and [slot=errorMessage]
CSS selectors, or by adding a custom className
.
Reusable wrappers#
If you will use a Select 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 Select
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
and errorMessage
slots to render help text (see below for details). The Item
component is also wrapped to apply class names based on the current state, as described above.
import {Text} from 'react-aria-components';
function MySelect({ label, description, errorMessage, children, ...props }) {
return (
<Select {...props}>
<Label>{label}</Label>
<Button>
<SelectValue />
<span aria-hidden="true">▼</span>
</Button>
{description && <Text slot="description">{description}</Text>}
{errorMessage && <Text slot="errorMessage">{errorMessage}</Text>}
<Popover>
<ListBox>
{children}
</ListBox>
</Popover>
</Select>
);
}
function MyItem(props) {
return (
<Item
{...props}
className={({ isFocused, isSelected }) =>
`my-item `}
/>
);
}
<MySelect label="Ice cream flavor">
<MyItem>Chocolate</MyItem>
<MyItem>Mint</MyItem>
<MyItem>Strawberry</MyItem>
<MyItem>Vanilla</MyItem>
</MySelect>
import {Text} from 'react-aria-components';
function MySelect(
{ label, description, errorMessage, children, ...props }
) {
return (
<Select {...props}>
<Label>{label}</Label>
<Button>
<SelectValue />
<span aria-hidden="true">▼</span>
</Button>
{description && (
<Text slot="description">{description}</Text>
)}
{errorMessage && (
<Text slot="errorMessage">{errorMessage}</Text>
)}
<Popover>
<ListBox>
{children}
</ListBox>
</Popover>
</Select>
);
}
function MyItem(props) {
return (
<Item
{...props}
className={({ isFocused, isSelected }) =>
`my-item `}
/>
);
}
<MySelect label="Ice cream flavor">
<MyItem>Chocolate</MyItem>
<MyItem>Mint</MyItem>
<MyItem>Strawberry</MyItem>
<MyItem>Vanilla</MyItem>
</MySelect>
import {Text} from 'react-aria-components';
function MySelect(
{
label,
description,
errorMessage,
children,
...props
}
) {
return (
<Select {...props}>
<Label>
{label}
</Label>
<Button>
<SelectValue />
<span aria-hidden="true">
▼
</span>
</Button>
{description && (
<Text slot="description">
{description}
</Text>
)}
{errorMessage && (
<Text slot="errorMessage">
{errorMessage}
</Text>
)}
<Popover>
<ListBox>
{children}
</ListBox>
</Popover>
</Select>
);
}
function MyItem(props) {
return (
<Item
{...props}
className={(
{
isFocused,
isSelected
}
) =>
`my-item `}
/>
);
}
<MySelect label="Ice cream flavor">
<MyItem>
Chocolate
</MyItem>
<MyItem>Mint</MyItem>
<MyItem>
Strawberry
</MyItem>
<MyItem>
Vanilla
</MyItem>
</MySelect>
Show CSS
.my-item {
margin: 2px;
padding: 4px 8px 4px 22px;
border-radius: 6px;
outline: none;
cursor: default;
color: var(--spectrum-global-color-gray-800);
font-size: 1.072rem;
position: relative;
&.selected {
font-weight: 600;
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&.focused {
background: #e70073;
color: white;
}
}
.my-item {
margin: 2px;
padding: 4px 8px 4px 22px;
border-radius: 6px;
outline: none;
cursor: default;
color: var(--spectrum-global-color-gray-800);
font-size: 1.072rem;
position: relative;
&.selected {
font-weight: 600;
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&.focused {
background: #e70073;
color: white;
}
}
.my-item {
margin: 2px;
padding: 4px 8px 4px 22px;
border-radius: 6px;
outline: none;
cursor: default;
color: var(--spectrum-global-color-gray-800);
font-size: 1.072rem;
position: relative;
&.selected {
font-weight: 600;
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&.focused {
background: #e70073;
color: white;
}
}
Usage#
The following examples show how to use the MySelect
component created in the above example.
Dynamic collections#
Select
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 Select using the items
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.
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'}
];
return (
<>
<MySelect label="Pick an engineering major" items={options}>
{(item) => <Item>{item.name}</Item>}
</MySelect>
</>
);
}
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' }
];
return (
<>
<MySelect
label="Pick an engineering major"
items={options}
>
{(item) => <Item>{item.name}</Item>}
</MySelect>
</>
);
}
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'
}
];
return (
<>
<MySelect
label="Pick an engineering major"
items={options}
>
{(item) => (
<Item>
{item.name}
</Item>
)}
</MySelect>
</>
);
}
Sections#
Select supports sections in order to group options. Sections can be used by wrapping groups of items in a Section
element. Each Section
takes a title
prop.
Static items
import {Section} from 'react-aria-components';
<MySelect label="Preferred fruit or vegetable">
<Section title="Fruit">
<Item id="Apple">Apple</Item>
<Item id="Banana">Banana</Item>
<Item id="Orange">Orange</Item>
<Item id="Honeydew">Honeydew</Item>
<Item id="Grapes">Grapes</Item>
<Item id="Watermelon">Watermelon</Item>
<Item id="Cantaloupe">Cantaloupe</Item>
<Item id="Pear">Pear</Item>
</Section>
<Section title="Vegetable">
<Item id="Cabbage">Cabbage</Item>
<Item id="Broccoli">Broccoli</Item>
<Item id="Carrots">Carrots</Item>
<Item id="Lettuce">Lettuce</Item>
<Item id="Spinach">Spinach</Item>
<Item id="Bok Choy">Bok Choy</Item>
<Item id="Cauliflower">Cauliflower</Item>
<Item id="Potatoes">Potatoes</Item>
</Section>
</MySelect>
import {Section} from 'react-aria-components';
<MySelect label="Preferred fruit or vegetable">
<Section title="Fruit">
<Item id="Apple">Apple</Item>
<Item id="Banana">Banana</Item>
<Item id="Orange">Orange</Item>
<Item id="Honeydew">Honeydew</Item>
<Item id="Grapes">Grapes</Item>
<Item id="Watermelon">Watermelon</Item>
<Item id="Cantaloupe">Cantaloupe</Item>
<Item id="Pear">Pear</Item>
</Section>
<Section title="Vegetable">
<Item id="Cabbage">Cabbage</Item>
<Item id="Broccoli">Broccoli</Item>
<Item id="Carrots">Carrots</Item>
<Item id="Lettuce">Lettuce</Item>
<Item id="Spinach">Spinach</Item>
<Item id="Bok Choy">Bok Choy</Item>
<Item id="Cauliflower">Cauliflower</Item>
<Item id="Potatoes">Potatoes</Item>
</Section>
</MySelect>
import {Section} from 'react-aria-components';
<MySelect label="Preferred fruit or vegetable">
<Section title="Fruit">
<Item id="Apple">
Apple
</Item>
<Item id="Banana">
Banana
</Item>
<Item id="Orange">
Orange
</Item>
<Item id="Honeydew">
Honeydew
</Item>
<Item id="Grapes">
Grapes
</Item>
<Item id="Watermelon">
Watermelon
</Item>
<Item id="Cantaloupe">
Cantaloupe
</Item>
<Item id="Pear">
Pear
</Item>
</Section>
<Section title="Vegetable">
<Item id="Cabbage">
Cabbage
</Item>
<Item id="Broccoli">
Broccoli
</Item>
<Item id="Carrots">
Carrots
</Item>
<Item id="Lettuce">
Lettuce
</Item>
<Item id="Spinach">
Spinach
</Item>
<Item id="Bok Choy">
Bok Choy
</Item>
<Item id="Cauliflower">
Cauliflower
</Item>
<Item id="Potatoes">
Potatoes
</Item>
</Section>
</MySelect>
Dynamic items
Sections used with dynamic items are populated from a hierarchical data structure. Similarly to the props on Select, <Section>
takes an array of data using the items
prop.
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 (
<MySelect label="Preferred fruit or vegetable" items={options}>
{item => (
<Section id={item.name} items={item.children} title={item.name}>
{item => <Item id={item.name}>{item.name}</Item>}
</Section>
)}
</MySelect>
);
}
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 (
<MySelect
label="Preferred fruit or vegetable"
items={options}
>
{(item) => (
<Section
id={item.name}
items={item.children}
title={item.name}
>
{(item) => <Item id={item.name}>{item.name}
</Item>}
</Section>
)}
</MySelect>
);
}
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 (
<MySelect
label="Preferred fruit or vegetable"
items={options}
>
{(item) => (
<Section
id={item.name}
items={item
.children}
title={item
.name}
>
{(item) => (
<Item
id={item
.name}
>
{item.name}
</Item>
)}
</Section>
)}
</MySelect>
);
}
Complex items#
Items within Select also allow for additional content used to better communicate options. Icons and descriptions can be added to the children
of Item
as shown in the example below.
If a description is added, the prop slot="description"
must be used to distinguish the different <Text>
elements.
import {Text} from 'react-aria-components';
<MySelect label="Permissions">
<Item textValue="Read">
<Text slot="label">Read</Text>
<Text slot="description">Read only</Text>
</Item>
<Item textValue="Write">
<Text slot="label">Write</Text>
<Text slot="description">Read and write only</Text>
</Item>
<Item textValue="Admin">
<Text slot="label">Admin</Text>
<Text slot="description">Full access</Text>
</Item>
</MySelect>
import {Text} from 'react-aria-components';
<MySelect label="Permissions">
<Item textValue="Read">
<Text slot="label">Read</Text>
<Text slot="description">Read only</Text>
</Item>
<Item textValue="Write">
<Text slot="label">Write</Text>
<Text slot="description">Read and write only</Text>
</Item>
<Item textValue="Admin">
<Text slot="label">Admin</Text>
<Text slot="description">Full access</Text>
</Item>
</MySelect>
import {Text} from 'react-aria-components';
<MySelect label="Permissions">
<Item textValue="Read">
<Text slot="label">
Read
</Text>
<Text slot="description">
Read only
</Text>
</Item>
<Item textValue="Write">
<Text slot="label">
Write
</Text>
<Text slot="description">
Read and write
only
</Text>
</Item>
<Item textValue="Admin">
<Text slot="label">
Admin
</Text>
<Text slot="description">
Full access
</Text>
</Item>
</MySelect>
Controlled selection#
Setting a selected option can be done by using the defaultSelectedKey
or selectedKey
prop. The selected key corresponds to the id
prop of an item.
When Select
is used with a dynamic collection as described above, the key of each item is derived from the data.
See the react-stately
Selection docs for more details.
function Example() {
let options = [
{name: 'Koala'},
{name: 'Kangaroo'},
{name: 'Platypus'},
{name: 'Bald Eagle'},
{name: 'Bison'},
{name: 'Skunk'}
];
let [animal, setAnimal] = React.useState("Bison");
return (
<MySelect
label="Pick an animal (controlled)"
items={options}
selectedKey={animal}
onSelectionChange={selected => setAnimal(selected)}>
{item => <Item id={item.name}>{item.name}</Item>}
</MySelect>
);
}
function Example() {
let options = [
{name: 'Koala'},
{name: 'Kangaroo'},
{name: 'Platypus'},
{name: 'Bald Eagle'},
{name: 'Bison'},
{name: 'Skunk'}
];
let [animal, setAnimal] = React.useState("Bison");
return (
<MySelect
label="Pick an animal (controlled)"
items={options}
selectedKey={animal}
onSelectionChange={selected => setAnimal(selected)}>
{item => <Item id={item.name}>{item.name}</Item>}
</MySelect>
);
}
function Example() {
let options = [
{ name: 'Koala' },
{ name: 'Kangaroo' },
{ name: 'Platypus' },
{
name: 'Bald Eagle'
},
{ name: 'Bison' },
{ name: 'Skunk' }
];
let [
animal,
setAnimal
] = React.useState(
'Bison'
);
return (
<MySelect
label="Pick an animal (controlled)"
items={options}
selectedKey={animal}
onSelectionChange={(selected) =>
setAnimal(
selected
)}
>
{(item) => (
<Item
id={item.name}
>
{item.name}
</Item>
)}
</MySelect>
);
}
Asynchronous loading#
This example uses the useAsyncList hook to handle asynchronous loading of data from a server. You may additionally want to display a spinner to indicate the loading state to the user, or support features like infinite scroll to load more data.
import {useAsyncList} from 'react-stately';
function AsyncLoadingExample() {
let list = useAsyncList({
async load({ signal, filterText }) {
let res = await fetch(
`https://pokeapi.co/api/v2/pokemon`,
{ signal }
);
let json = await res.json();
return {
items: json.results
};
}
});
return (
<MySelect label="Pick a Pokemon" items={list.items} selectionMode="single">
{(item) => <Item id={item.name}>{item.name}</Item>}
</MySelect>
);
}
import {useAsyncList} from 'react-stately';
function AsyncLoadingExample() {
let list = useAsyncList({
async load({ signal, filterText }) {
let res = await fetch(
`https://pokeapi.co/api/v2/pokemon`,
{ signal }
);
let json = await res.json();
return {
items: json.results
};
}
});
return (
<MySelect
label="Pick a Pokemon"
items={list.items}
selectionMode="single"
>
{(item) => <Item id={item.name}>{item.name}</Item>}
</MySelect>
);
}
import {useAsyncList} from 'react-stately';
function AsyncLoadingExample() {
let list =
useAsyncList({
async load(
{
signal,
filterText
}
) {
let res =
await fetch(
`https://pokeapi.co/api/v2/pokemon`,
{ signal }
);
let json =
await res
.json();
return {
items:
json.results
};
}
});
return (
<MySelect
label="Pick a Pokemon"
items={list.items}
selectionMode="single"
>
{(item) => (
<Item
id={item.name}
>
{item.name}
</Item>
)}
</MySelect>
);
}
Disabled#
A Select can be fully disabled using the isDisabled
prop.
<MySelect label="Choose frequency" isDisabled>
<Item id="rarely">Rarely</Item>
<Item id="sometimes">Sometimes</Item>
<Item id="always">Always</Item>
</MySelect>
<MySelect label="Choose frequency" isDisabled>
<Item id="rarely">Rarely</Item>
<Item id="sometimes">Sometimes</Item>
<Item id="always">Always</Item>
</MySelect>
<MySelect
label="Choose frequency"
isDisabled
>
<Item id="rarely">
Rarely
</Item>
<Item id="sometimes">
Sometimes
</Item>
<Item id="always">
Always
</Item>
</MySelect>
Disabled options#
Select
supports marking items as disabled using the disabledKeys
prop. Each key in this list
corresponds with the id
prop passed to the Item
component, or automatically derived from the values passed
to the items
prop. See Collections for more details.
Disabled items are not focusable, selectable, or keyboard navigable. The isDisabled
property returned by
useOption
can be used to style the item appropriately.
<MySelect label="Favorite Animal" disabledKeys={['cat', 'kangaroo']}>
<Item id="red panda">Red Panda</Item>
<Item id="cat">Cat</Item>
<Item id="dog">Dog</Item>
<Item id="aardvark">Aardvark</Item>
<Item id="kangaroo">Kangaroo</Item>
<Item id="snake">Snake</Item>
</MySelect>
<MySelect
label="Favorite Animal"
disabledKeys={['cat', 'kangaroo']}
>
<Item id="red panda">Red Panda</Item>
<Item id="cat">Cat</Item>
<Item id="dog">Dog</Item>
<Item id="aardvark">Aardvark</Item>
<Item id="kangaroo">Kangaroo</Item>
<Item id="snake">Snake</Item>
</MySelect>
<MySelect
label="Favorite Animal"
disabledKeys={[
'cat',
'kangaroo'
]}
>
<Item id="red panda">
Red Panda
</Item>
<Item id="cat">
Cat
</Item>
<Item id="dog">
Dog
</Item>
<Item id="aardvark">
Aardvark
</Item>
<Item id="kangaroo">
Kangaroo
</Item>
<Item id="snake">
Snake
</Item>
</MySelect>
Controlled open state#
The open state of the select can be controlled via the defaultOpen
and isOpen
props
function Example() {
let [open, setOpen] = React.useState(false);
return (
<>
<p>Select is {open ? 'open' : 'closed'}</p>
<MySelect label="Choose frequency" isOpen={open} onOpenChange={setOpen}>
<Item id="rarely">Rarely</Item>
<Item id="sometimes">Sometimes</Item>
<Item id="always">Always</Item>
</MySelect>
</>
);
}
function Example() {
let [open, setOpen] = React.useState(false);
return (
<>
<p>Select is {open ? 'open' : 'closed'}</p>
<MySelect
label="Choose frequency"
isOpen={open}
onOpenChange={setOpen}
>
<Item id="rarely">Rarely</Item>
<Item id="sometimes">Sometimes</Item>
<Item id="always">Always</Item>
</MySelect>
</>
);
}
function Example() {
let [open, setOpen] =
React.useState(
false
);
return (
<>
<p>
Select is {open
? 'open'
: 'closed'}
</p>
<MySelect
label="Choose frequency"
isOpen={open}
onOpenChange={setOpen}
>
<Item id="rarely">
Rarely
</Item>
<Item id="sometimes">
Sometimes
</Item>
<Item id="always">
Always
</Item>
</MySelect>
</>
);
}
Help text#
The description
slot can be used to associate additional help text with a Select. Additionally, the errorMessage
slot can be used to help the user fix a validation error. It should be combined with the validationState
prop to semantically mark the Select as invalid for assistive technologies.
function Example() {
let [animalId, setAnimalId] = React.useState();
let options = [
{ id: 1, name: 'Aardvark' },
{ id: 2, name: 'Cat' },
{ id: 3, name: 'Dog' },
{ id: 4, name: 'Kangaroo' },
{ id: 5, name: 'Koala' },
{ id: 6, name: 'Penguin' },
{ id: 7, name: 'Snake' },
{ id: 8, name: 'Turtle' },
{ id: 9, name: 'Wombat' }
];
let isValid = React.useMemo(() => animalId !== 2 && animalId !== 7, [
animalId
]);
return (
<MySelect
validationState={isValid ? 'valid' : 'invalid'}
label="Favorite animal"
description={isValid
? 'Pick your favorite animal, you will be judged.'
: null}
errorMessage={isValid
? null
: animalId === 2
? 'The author of this example is a dog person.'
: "Oh no it's a snake! Choose anything else."}
items={options}
selectedKey={animalId}
onSelectionChange={(selected) => setAnimalId(selected)}
>
{(item) => <Item>{item.name}</Item>}
</MySelect>
);
}
function Example() {
let [animalId, setAnimalId] = React.useState();
let options = [
{ id: 1, name: 'Aardvark' },
{ id: 2, name: 'Cat' },
{ id: 3, name: 'Dog' },
{ id: 4, name: 'Kangaroo' },
{ id: 5, name: 'Koala' },
{ id: 6, name: 'Penguin' },
{ id: 7, name: 'Snake' },
{ id: 8, name: 'Turtle' },
{ id: 9, name: 'Wombat' }
];
let isValid = React.useMemo(
() => animalId !== 2 && animalId !== 7,
[
animalId
]
);
return (
<MySelect
validationState={isValid ? 'valid' : 'invalid'}
label="Favorite animal"
description={isValid
? 'Pick your favorite animal, you will be judged.'
: null}
errorMessage={isValid
? null
: animalId === 2
? 'The author of this example is a dog person.'
: "Oh no it's a snake! Choose anything else."}
items={options}
selectedKey={animalId}
onSelectionChange={(selected) =>
setAnimalId(selected)}
>
{(item) => <Item>{item.name}</Item>}
</MySelect>
);
}
function Example() {
let [
animalId,
setAnimalId
] = React.useState();
let options = [
{
id: 1,
name: 'Aardvark'
},
{
id: 2,
name: 'Cat'
},
{
id: 3,
name: 'Dog'
},
{
id: 4,
name: 'Kangaroo'
},
{
id: 5,
name: 'Koala'
},
{
id: 6,
name: 'Penguin'
},
{
id: 7,
name: 'Snake'
},
{
id: 8,
name: 'Turtle'
},
{
id: 9,
name: 'Wombat'
}
];
let isValid = React
.useMemo(
() =>
animalId !== 2 &&
animalId !== 7,
[
animalId
]
);
return (
<MySelect
validationState={isValid
? 'valid'
: 'invalid'}
label="Favorite animal"
description={isValid
? 'Pick your favorite animal, you will be judged.'
: null}
errorMessage={isValid
? null
: animalId === 2
? 'The author of this example is a dog person.'
: "Oh no it's a snake! Choose anything else."}
items={options}
selectedKey={animalId}
onSelectionChange={(
selected
) =>
setAnimalId(
selected
)}
>
{(item) => (
<Item>
{item.name}
</Item>
)}
</MySelect>
);
}
Advanced customization#
Composition#
If you need to customize one of the components within a Select
, such as Button
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"
/>
);
}
Hooks#
If you need to customize things even further, such as accessing internal state or customizing DOM structure, you can drop down to the lower
level Hook-based API. Select
sends props to its child elements via public React contexts for each component. You can use this context to
implement replacements for any component, using hooks from react-aria
. This allows you to replace only the components you need to customize,
and keep using the others.
Select
uses the following hooks. See the linked documentation for more details.
To replace a component used within a Select
, create your own component and use the useContextProps
hook to merge the local props and ref with the ones sent via context by Select
. This example shows how you could implement a custom ListBox
component that works with Select
.
import {ListBoxContext, useContextProps} from 'react-aria-components';
import {useListBox} from 'react-aria';
function MyListBox(props) {
// Merge local props and ref with props from context.
let ref = React.useRef();
[props, ref] = useContextProps(props, ref, ListBoxContext);
// Get state sent from Select via context, and call useListBox.
let {state} = React.useContext(ListBoxContext);
let {listBoxProps} = useListBox(props, state, ref);
// Render stuff
// ...
}
import {
ListBoxContext,
useContextProps
} from 'react-aria-components';
import {useListBox} from 'react-aria';
function MyListBox(props) {
// Merge local props and ref with props from context.
let ref = React.useRef();
[props, ref] = useContextProps(
props,
ref,
ListBoxContext
);
// Get state sent from Select via context, and call useListBox.
let { state } = React.useContext(ListBoxContext);
let { listBoxProps } = useListBox(props, state, ref);
// Render stuff
// ...
}
import {
ListBoxContext,
useContextProps
} from 'react-aria-components';
import {useListBox} from 'react-aria';
function MyListBox(
props
) {
// Merge local props and ref with props from context.
let ref = React
.useRef();
[props, ref] =
useContextProps(
props,
ref,
ListBoxContext
);
// Get state sent from Select via context, and call useListBox.
let { state } = React
.useContext(
ListBoxContext
);
let { listBoxProps } =
useListBox(
props,
state,
ref
);
// Render stuff
// ...
}
This also works the other way. If you need to customize Select
itself, but want to reuse the components it contains, you can do so by providing the necessary contexts. The Provider
component is an easier way to send multiple contexts at once.
import {ButtonContext, LabelContext, ListBoxContext, PopoverContext, Provider} from 'react-aria-components';
import {useSelect} from 'react-aria';
function MySelect(props) {
// ...
let ref = useRef(null);
let {
labelProps,
triggerProps,
valueProps,
menuProps
} = useSelect({/* ... */});
return (
<Provider
values={[
[LabelContext, labelProps],
[ButtonContext, { ...triggerProps, ref }],
[PopoverContext, { state, triggerRef: ref }],
[ListBoxContext, { ...listBoxProps, ref: listBoxRef }]
]}
>
{props.children}
</Provider>
);
}
import {
ButtonContext,
LabelContext,
ListBoxContext,
PopoverContext,
Provider
} from 'react-aria-components';
import {useSelect} from 'react-aria';
function MySelect(props) {
// ...
let ref = useRef(null);
let {
labelProps,
triggerProps,
valueProps,
menuProps
} = useSelect({/* ... */});
return (
<Provider
values={[
[LabelContext, labelProps],
[ButtonContext, { ...triggerProps, ref }],
[PopoverContext, { state, triggerRef: ref }],
[ListBoxContext, {
...listBoxProps,
ref: listBoxRef
}]
]}
>
{props.children}
</Provider>
);
}
import {
ButtonContext,
LabelContext,
ListBoxContext,
PopoverContext,
Provider
} from 'react-aria-components';
import {useSelect} from 'react-aria';
function MySelect(
props
) {
// ...
let ref = useRef(null);
let {
labelProps,
triggerProps,
valueProps,
menuProps
} = useSelect({
/* ... */
});
return (
<Provider
values={[
[
LabelContext,
labelProps
],
[ButtonContext, {
...triggerProps,
ref
}],
[
PopoverContext,
{
state,
triggerRef:
ref
}
],
[
ListBoxContext,
{
...listBoxProps,
ref:
listBoxRef
}
]
]}
>
{props.children}
</Provider>
);
}