{/* Copyright 2020 Adobe. All rights reserved. This file is licensed to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */}
import {Layout} from '@react-spectrum/docs'; export default Layout;
import docs from 'docs:react-aria-components'; import statelyDocs from 'docs:@react-stately/select'; import {PropTable, HeaderInfo, TypeLink, PageDescription, StateTable, ContextTable} from '@react-spectrum/docs'; import styles from '@react-spectrum/docs/src/docs.css'; import packageData from 'react-aria-components/package.json'; import Anatomy from './SelectAnatomy.svg'; import ChevronRight from '@spectrum-icons/workflow/ChevronRight'; import {Divider} from '@react-spectrum/divider'; import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard'; import Button from '@react-spectrum/docs/pages/assets/component-illustrations/ActionButton.svg'; import Label from '@react-spectrum/docs/pages/assets/component-illustrations/Label.svg'; import Popover from '@react-spectrum/docs/pages/assets/component-illustrations/Popover.svg'; import ListBox from '@react-spectrum/docs/pages/assets/component-illustrations/ListBox.svg'; import Collections from '@react-spectrum/docs/pages/assets/component-illustrations/Collections.svg'; import Selection from '@react-spectrum/docs/pages/assets/component-illustrations/Selection.svg';
{docs.exports.Select.description}
<HeaderInfo packageData={packageData} componentNames={['Select']} sourceData={[ {type: 'W3C', url: 'https://www.w3.org/WAI/ARIA/apg/patterns/listbox/'} ]} />
import {Select, SelectValue, Label, Button, Popover, ListBox, Item} 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 {
--border-color: var(--spectrum-alias-border-color);
--border-color-disabled: var(--spectrum-alias-border-color-disabled);
--text-color: var(--spectrum-alias-text-color);
--text-color-disabled: var(--spectrum-alias-text-color-disabled);
--focus-ring-color: slateblue;
.react-aria-Button {
color: var(--text-color);
background: var(--spectrum-global-color-gray-50);
border: 1px solid var(--border-color);
box-shadow: 0 1px 2px rgba(0 0 0 / 0.1);
border-radius: 6px;
appearance: none;
vertical-align: middle;
font-size: 1.072rem;
padding: 0.286rem 0.286rem 0.286rem 0.571rem;
margin: 0;
outline: none;
display: flex;
align-items: center;
max-width: 250px;
&[data-focus-visible] {
border-color: var(--focus-ring-color);
box-shadow: 0 0 0 1px var(--focus-ring-color);
}
&[data-pressed] {
background: var(--spectrum-global-color-gray-150);
}
&[data-disabled] {
border-color: var(--border-color-disabled);
color: var(--text-color-disabled);
& span[aria-hidden] {
background: var(--border-color-disabled);
}
.react-aria-SelectValue {
&[data-placeholder] {
color: var(--text-color-disabled);
}
}
}
}
.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 {
--highlight-background: slateblue;
--highlight-foreground: white;
--text-color: var(--spectrum-alias-text-color);
--text-color-disabled: var(--spectrum-alias-text-color-disabled);
max-height: inherit;
box-sizing: border-box;
overflow: auto;
padding: 2px;
outline: none;
.react-aria-Section:not(:first-child) {
margin-top: 12px;
}
.react-aria-Header {
font-size: 1.143rem;
font-weight: bold;
padding: 0 0.571rem 0 1.571rem;
}
.react-aria-Item {
margin: 2px;
padding: 0.286rem 0.571rem 0.286rem 1.571rem;
border-radius: 6px;
outline: none;
cursor: default;
color: var(--text-color);
font-size: 1.072rem;
position: relative;
display: flex;
flex-direction: column;
&[data-selected] {
font-weight: 600;
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&[data-focused],
&[data-pressed] {
background: var(--highlight-background);
color: var(--highlight-foreground);
}
&[data-disabled] {
color: var(--text-color-disabled);
}
[slot=label] {
font-weight: bold;
}
[slot=description] {
font-size: small;
}
}
}
.react-aria-Popover {
--background-color: var(--page-background);
--border-color: var(--spectrum-global-color-gray-400);
border: 1px solid var(--border-color);
min-width: var(--trigger-width);
max-width: 250px;
box-sizing: border-box;
box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
border-radius: 6px;
background: var(--background-color);
outline: none;
&[data-placement=top] {
--origin: translateY(8px);
}
&[data-placement=bottom] {
--origin: translateY(-8px);
}
&[data-entering] {
animation: slide 200ms;
}
&[data-exiting] {
animation: slide 200ms reverse ease-in;
}
}
@keyframes slide {
from {
transform: var(--origin);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@media (forced-colors: active) {
.react-aria-Select {
--border-color: ButtonBorder;
--border-color-disabled: GrayText;
--text-color: ButtonText;
--text-color-disabled: GrayText;
--focus-ring-color: Highlight;
.react-aria-Button:disabled span[aria-hidden] {
background: transparent;
}
}
.react-aria-ListBox {
forced-color-adjust: none;
--highlight-background: Highlight;
--highlight-foreground: HighlightText;
--border-color: ButtonBorder;
--background-color: ButtonFace;
--text-color: ButtonText;
--text-color-disabled: GrayText;
}
}
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.
A select consists of a label, a button which displays a selected value, and a listbox, displayed in a popover. Users can click, touch, or use the keyboard on the button to open the listbox popover.
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.
import {Select, SelectValue, Label, Button, Popover, ListBox, Item, Section, Header, Text} from 'react-aria-components';
<Select>
<Label />
<Button>
<SelectValue />
</Button>
<Text slot="description" />
<Text slot="errorMessage" />
<Popover>
<ListBox>
<Item>
<Text slot="label" />
<Text slot="description" />
</Item>
<Section>
<Header />
<Item />
</Section>
</ListBox>
</Popover>
</Select>
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.
Select
makes use of the following concepts:
A Select
uses the following components, which may also be used standalone or reused in other components.
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 in the styling section.
import type {SelectProps, ItemProps} from 'react-aria-components';
import {Text} from 'react-aria-components';
interface MySelectProps<T extends object> extends Omit<SelectProps<T>, 'children'> {
label?: string,
description?: string,
errorMessage?: string,
items?: Iterable<T>,
children: React.ReactNode | ((item: T) => React.ReactNode)
}
function MySelect<T extends object>({label, description, errorMessage, children, items, ...props}: MySelectProps<T>) {
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 items={items}>
{children}
</ListBox>
</Popover>
</Select>
);
}
function MyItem(props: ItemProps) {
return <Item {...props} className={({isFocused, isSelected}) => `my-item ${isFocused ? 'focused' : ''} ${isSelected ? 'selected' : ''}`} />
}
<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(--text-color);
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;
}
}
@media (forced-colors: active) {
.my-item.focused {
background: Highlight;
color: HighlightText;
}
}
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>
);
}
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 id 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<React.Key>("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>
);
}
Select supports the name
prop for integration with HTML forms. The id
of the selected item will be submitted to the server.
<MySelect
label="Favorite Animal"
///- begin highlight -///
name="favoriteAnimalId"
///- end highlight -///
>
<Item id="panda">Panda</Item>
<Item id="cat">Cat</Item>
<Item id="dog">Dog</Item>
</MySelect>
By default, interacting with an item in a Select triggers onSelectionChange
. Alternatively, items may be links to another page or website. This can be achieved by passing the href
prop to the <Item>
component. Link items in a Select
are not selectable.
<MySelect label="Project">
<Item href="https://example.com/" target="_blank">Create new…</Item>
<Item>Proposal</Item>
<Item>Budget</Item>
<Item>Onboarding</Item>
</MySelect>
.react-aria-Item[href] {
text-decoration: none;
cursor: pointer;
}
The <Item>
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 component at the root of your app. See the client side routing guide to learn how to set this up.
Select supports sections in order to group options. Sections can be used by wrapping groups of items in a Section
element. A <Header>
element may also be included to label the section.
import {Section, Header} from 'react-aria-components';
<MySelect label="Preferred fruit or vegetable">
<Section>
<Header>Fruit</Header>
<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>
<Header>Vegetable</Header>
<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>
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. If the section also has a header,
the 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 (
<MySelect label="Preferred fruit or vegetable" items={options}>
{section => (
<Section id={section.name}>
<Header>{section.name}</Header>
<Collection items={section.children}>
{item => <Item id={item.name}>{item.name}</Item>}
</Collection>
</Section>
)}
</MySelect>
);
}
By default, items in a Select are labeled by their text contents for accessibility. Items 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';
<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>
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.
import {useAsyncList} from '@react-stately/data';
interface Character {
name: string
}
function AsyncLoadingExample() {
let list = useAsyncList<Character>({
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}>
{(item) => <Item id={item.name}>{item.name}</Item>}
</MySelect>
);
}
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>
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>
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 isInvalid
prop to semantically mark the Select as invalid for assistive technologies.
function Example() {
let [animalId, setAnimalId] = React.useState<React.Key | null>(null);
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 (
<Select selectedKey={animalId} onSelectionChange={setAnimalId} isInvalid={!isValid}>
<Label>Favorite Animal</Label>
<Button>
<SelectValue />
<span aria-hidden="true">▼</span>
</Button>
{/*- begin highlight -*/}
{isValid && <Text slot="description">Pick your favorite animal, you will be judged.</Text>}
{!isValid && <Text slot="errorMessage">{animalId === 2
? 'The author of this example is a dog person.'
: "Oh no it's a snake! Choose anything else."}</Text>}
{/*- end highlight -*/}
<Popover>
<ListBox items={options}>
{item => <Item>{item.name}</Item>}
</ListBox>
</Popover>
</Select>
);
}
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>
</>
);
}
A <Label>
accepts all HTML attributes.
A <Button>
accepts its contents as children
. Other props such as onPress
and isDisabled
will be set by the Select
.
Show props
The <SelectValue>
component displays the current value of the select within the <Button>
, or a placeholder if no value is selected.
Show props
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
Within a <Select>
, most <ListBox>
props are set automatically. The <ListBox>
defines the options to display in a Select.
Show props
A <Section>
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
A <Header>
defines the title for a <Section>
. It accepts all DOM attributes.
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
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 {
/* ... */
}
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>
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-Item[data-selected] {
/* ... */
}
.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>
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>
The states and selectors for each component used in a Select
are documented below.
A Select
can be targeted with the .react-aria-Select
CSS selector, or by overriding with a custom className
. It supports the following states:
A Label
can be targeted with the .react-aria-Label
CSS selector, or by overriding with a custom className
.
A Button can be targeted with the .react-aria-Button
CSS selector, or by overriding with a custom className
. It supports the following states:
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:
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 --trigger-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(--trigger-width);
}
A ListBox
can be targeted with the .react-aria-ListBox
CSS selector, or by overriding with a custom className
.
A Section
can be targeted with the .react-aria-Section
CSS selector, or by overriding with a custom className
. See sections for examples.
A Header
within a Section
can be targeted with the .react-aria-Header
CSS selector, or by overriding with a custom className
. See sections for examples.
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:
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.
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
.
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" />
}
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).
<ContextTable components={['Select']} docs={docs} />
This example shows a FieldGroup
component that renders a group of selects with a title. The entire group can be marked as disabled via the isDisabled
prop, which is passed to all child selects via the SelectContext
provider.
import {SelectContext} from 'react-aria-components';
interface FieldGroupProps {
title?: string,
children?: React.ReactNode,
isDisabled?: boolean
}
function FieldGroup({title, children, isDisabled}: FieldGroupProps) {
return (
<fieldset>
<legend>{title}</legend>
{/*- begin highlight -*/}
<SelectContext.Provider value={{isDisabled}}>
{/*- end highlight -*/}
{children}
</SelectContext.Provider>
</fieldset>
);
}
<FieldGroup title="Filters" isDisabled>
<MySelect label="Status" defaultSelectedKey="published">
<Item id="draft">Draft</Item>
<Item id="published">Published</Item>
<Item id="deleted">Deleted</Item>
</MySelect>
<MySelect label="Author" defaultSelectedKey="emma">
<Item id="john">John</Item>
<Item id="emma">Emma</Item>
<Item id="tim">Tim</Item>
</MySelect>
</FieldGroup>
Show CSS
fieldset {
padding: 1.5em;
width: fit-content;
}
Select passes props to its child components, such as the label and popover, 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.
<ContextTable components={['Label', 'Button', 'Popover', 'ListBox', 'Text']} docs={docs} />
This example consumes from LabelContext
in an existing styled label component to make it compatible with React Aria Components. The hook merges the local props and ref with the ones provided via context by Select.
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.
///- begin highlight -///
[props, ref] = useContextProps(props, ref, LabelContext);
///- end highlight -///
// ... your existing Label component
return <label {...props} ref={ref} />;
});
Now you can use MyCustomLabel
within a Select
, in place of the builtin React Aria Components Label
.
<Select>
{/*- begin highlight -*/}
<MyCustomLabel>Name</MyCustomLabel>
{/*- end highlight -*/}
{/* ... */}
</Select>
Select provides an object to its children via SelectStateContext
. This can be used to access and manipulate the select's state.
This example shows a SelectClearButton
component that can be placed within a Select
to allow the user to clear the selected item.
import {SelectStateContext} from 'react-aria-components';
function SelectClearButton() {
/*- begin highlight -*/
let state = React.useContext(SelectStateContext);
/*- end highlight -*/
return (
<Button
// Don't inherit behavior from Select.
slot={null}
style={{fontSize: 'small', marginTop: 6, padding: 4}}
onPress={() => state?.setSelectedKey(null)}>
Clear
</Button>
);
}
<Select>
<Label>Favorite Animal</Label>
<Button>
<SelectValue />
<span aria-hidden="true">▼</span>
</Button>
{/*- begin highlight -*/}
<SelectClearButton />
{/*- end highlight -*/}
<Popover>
<ListBox>
<Item>Cat</Item>
<Item>Dog</Item>
<Item>Kangaroo</Item>
</ListBox>
</Popover>
</Select>
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 useSelect for more details.
Select may trigger a known accessibility false positive
from automated accessibility testing tools. This is because we include a visually hidden select element alongside the Select to specifically aid with browser form autocomplete
and hide it from screen readers via aria-hidden
since users don't need to interact with the hidden select. We manage focus internally
so that focusing this hidden select will always shift focus to the Select's trigger button instead. Automated accessibility testing tools have no way of
knowing that we manage the focus in this way and thus throw this false positive.
To facilitate the suppression of this false positive, the data-a11y-ignore="aria-hidden-focus"
data attribute is automatically applied to the
problematic element and references the relevant AXE
rule. Please use this data attribute to target the problematic element and exclude it
from your automated accessibility tests as shown here.