Provides the behavior and accessibility implementation for a select component.
A select displays a collapsible list of options and allows a user to select one of them.
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. useSelect helps achieve accessible
select components that can be styled as needed without compromising on high quality interactions.
Exposed to assistive technology as a button with a listbox popup using ARIA (combined with useListBox)
Support for selecting a single option
Support for disabled options
Support for sections
Labeling support for accessibility
Support for mouse, touch, and keyboard interactions
Tab stop focus management
Keyboard support for opening the listbox using the arrow keys, including automatically focusing
the first or last item accordingly
Typeahead to allow selecting options by typing text, even without opening the listbox
Browser autofill integration via a hidden native <select> element
Support for mobile form navigation via software keyboard
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. useSelect
handles exposing the correct ARIA attributes for accessibility and handles the interactions for the
select in its collapsed state. It should be combined with useListBox, which handles
the implementation of the popup listbox.
useSelect returns props that you should spread onto the appropriate element:
useSelect requires knowledge of the options in the select in order to handle keyboard
navigation and other interactions. It does this using the Collection
interface, which is a generic interface to access sequential unique keyed data. You can
implement this interface yourself, e.g. by using a prop to pass a list of item objects,
but useSelectState from
@react-stately/select implements a JSX based interface for building collections instead.
See Collection Components for more information,
and Collection Interface for internal details.
In addition, useSelectState
manages the state necessary for multiple selection and exposes
a SelectionManager,
which makes use of the collection to provide an interface to update the selection state.
It also holds state to track if the popup is open.
For more information about selection, see Selection.
This example uses a <button> element for the trigger, with a <span> inside to hold the value,
and another for the dropdown arrow icon (hidden from screen readers with aria-hidden).
A <HiddenSelect> is used to render a hidden native
<select>, which enables browser form autofill support.
The listbox popup uses useListBox
and useOption to render the
list of options. In addition, a <FocusScope>
is used to automatically restore focus to the trigger
when the popup closes. A hidden <DismissButton>
is added at the start and end of the popup to allow screen reader users to dismiss the popup.
This example does not do any advanced popover positioning or portaling to escape its visual container.
See useOverlayTrigger for an example of how to implement this
using useOverlayPosition.
In addition, see useListBox for examples of sections (option groups), and more complex
options.
import{useSelectState}from'@react-stately/select';import{Item}from'@react-stately/collections';import{HiddenSelect}from'@react-aria/select';import{useListBox, useOption}from'@react-aria/listbox';import{mergeProps}from'@react-aria/utils';import{useButton}from'@react-aria/button';import{useFocus}from'@react-aria/interactions';import{FocusScope}from'@react-aria/focus';import{useOverlay,DismissButton}from'@react-aria/overlays';functionSelect(props){// Create state based on the incoming propslet state =useSelectState(props);// Get props for child elements from useSelectlet ref =React.useRef();let{
labelProps,
triggerProps,
valueProps,
menuProps
}=useSelect(props, state, ref);// Get props for the button based on the trigger props from useSelectlet{buttonProps}=useButton(triggerProps);return(<divstyle={{position:'relative',display:'inline-block'}}><div{...labelProps}>{props.label}</div><HiddenSelectstate={state}triggerRef={ref}label={props.label}name={props.name}/><button{...buttonProps}ref={ref}style={{height:30,fontSize:14}}><span{...valueProps}>{state.selectedItem? state.selectedItem.rendered:'Select an option'}</span><spanaria-hidden="true"style={{paddingLeft:5}}>
▼
</span></button>{state.isOpen&&<ListBoxPopup{...menuProps}state={state}/>}</div>);}functionListBoxPopup({state,...otherProps}){let ref =React.useRef();// Get props for the listboxlet{listBoxProps}=useListBox({autoFocus: state.focusStrategy,disallowEmptySelection:true}, state, ref);// Handle events that should cause the popup to close,// e.g. blur, clicking outside, or pressing the escape key.let overlayRef =React.useRef();let{overlayProps}=useOverlay({onClose:()=> state.close(),shouldCloseOnBlur:true,isOpen: state.isOpen,isDismissable:true}, overlayRef);// Wrap in <FocusScope> so that focus is restored back to the// trigger when the popup is closed. In addition, add hidden// <DismissButton> components at the start and end of the list// to allow screen reader users to dismiss the popup easily.return(<FocusScoperestoreFocus><div{...overlayProps}ref={overlayRef}><DismissButtononDismiss={()=> state.close()}/><ul{...mergeProps(listBoxProps, otherProps)}ref={ref}style={{position:'absolute',width:'100%',margin:'4px 0 0 0',padding:0,listStyle:'none',border:'1px solid gray',background:'lightgray'}}>{[...state.collection].map(item =>(<Optionkey={item.key}item={item}state={state}/>))}</ul><DismissButtononDismiss={()=> state.close()}/></div></FocusScope>)}functionOption({item, state}){// Get props for the option elementlet ref =React.useRef();let isDisabled = state.disabledKeys.has(item.key);let isSelected = state.selectionManager.isSelected(item.key);let{optionProps}=useOption({key: item.key,
isDisabled,
isSelected,shouldSelectOnPressUp:true,shouldFocusOnHover:true}, state, ref);// Handle focus events so we can apply highlighted// style to the focused optionlet[isFocused, setFocused]=React.useState(false);let{focusProps}=useFocus({onFocusChange: setFocused});return(<li{...mergeProps(optionProps, focusProps)}ref={ref}style={{background: isSelected
?'blueviolet': isFocused
?'gray':'transparent',color: isSelected || isFocused ?'white':'black',padding:'2px 5px',outline:'none',cursor:'pointer'}}>{item.rendered}</li>);}<Selectlabel="Favorite Color"><Item>Red</Item><Item>Orange</Item><Item>Yellow</Item><Item>Green</Item><Item>Blue</Item><Item>Purple</Item></Select>
import{useSelectState}from'@react-stately/select';import{Item}from'@react-stately/collections';import{HiddenSelect}from'@react-aria/select';import{useListBox, useOption}from'@react-aria/listbox';import{mergeProps}from'@react-aria/utils';import{useButton}from'@react-aria/button';import{useFocus}from'@react-aria/interactions';import{FocusScope}from'@react-aria/focus';import{
useOverlay,DismissButton}from'@react-aria/overlays';functionSelect(props){// Create state based on the incoming propslet state =useSelectState(props);// Get props for child elements from useSelectlet ref =React.useRef();let{
labelProps,
triggerProps,
valueProps,
menuProps
}=useSelect(props, state, ref);// Get props for the button based on the trigger props from useSelectlet{buttonProps}=useButton(triggerProps);return(<divstyle={{position:'relative',display:'inline-block'}}><div{...labelProps}>{props.label}</div><HiddenSelectstate={state}triggerRef={ref}label={props.label}name={props.name}/><button{...buttonProps}ref={ref}style={{height:30,fontSize:14}}><span{...valueProps}>{state.selectedItem? state.selectedItem.rendered:'Select an option'}</span><spanaria-hidden="true"style={{paddingLeft:5}}>
▼
</span></button>{state.isOpen&&(<ListBoxPopup{...menuProps}state={state}/>)}</div>);}functionListBoxPopup({state,...otherProps}){let ref =React.useRef();// Get props for the listboxlet{listBoxProps}=useListBox({autoFocus: state.focusStrategy,disallowEmptySelection:true},
state,
ref
);// Handle events that should cause the popup to close,// e.g. blur, clicking outside, or pressing the escape key.let overlayRef =React.useRef();let{overlayProps}=useOverlay({onClose:()=> state.close(),shouldCloseOnBlur:true,isOpen: state.isOpen,isDismissable:true},
overlayRef
);// Wrap in <FocusScope> so that focus is restored back to the// trigger when the popup is closed. In addition, add hidden// <DismissButton> components at the start and end of the list// to allow screen reader users to dismiss the popup easily.return(<FocusScoperestoreFocus><div{...overlayProps}ref={overlayRef}><DismissButtononDismiss={()=> state.close()}/><ul{...mergeProps(listBoxProps, otherProps)}ref={ref}style={{position:'absolute',width:'100%',margin:'4px 0 0 0',padding:0,listStyle:'none',border:'1px solid gray',background:'lightgray'}}>{[...state.collection].map((item)=>(<Optionkey={item.key}item={item}state={state}/>))}</ul><DismissButtononDismiss={()=> state.close()}/></div></FocusScope>);}functionOption({item, state}){// Get props for the option elementlet ref =React.useRef();let isDisabled = state.disabledKeys.has(item.key);let isSelected = state.selectionManager.isSelected(
item.key);let{optionProps}=useOption({key: item.key,
isDisabled,
isSelected,shouldSelectOnPressUp:true,shouldFocusOnHover:true},
state,
ref
);// Handle focus events so we can apply highlighted// style to the focused optionlet[isFocused, setFocused]=React.useState(false);let{focusProps}=useFocus({onFocusChange: setFocused});return(<li{...mergeProps(optionProps, focusProps)}ref={ref}style={{background: isSelected
?'blueviolet': isFocused
?'gray':'transparent',color: isSelected || isFocused ?'white':'black',padding:'2px 5px',outline:'none',cursor:'pointer'}}>{item.rendered}</li>);}<Selectlabel="Favorite Color"><Item>Red</Item><Item>Orange</Item><Item>Yellow</Item><Item>Green</Item><Item>Blue</Item><Item>Purple</Item></Select>
import{useSelectState}from'@react-stately/select';import{Item}from'@react-stately/collections';import{HiddenSelect}from'@react-aria/select';import{
useListBox,
useOption
}from'@react-aria/listbox';import{mergeProps}from'@react-aria/utils';import{useButton}from'@react-aria/button';import{useFocus}from'@react-aria/interactions';import{FocusScope}from'@react-aria/focus';import{
useOverlay,DismissButton}from'@react-aria/overlays';functionSelect(props){// Create state based on the incoming propslet state =useSelectState(
props
);// Get props for child elements from useSelectlet ref =React.useRef();let{
labelProps,
triggerProps,
valueProps,
menuProps
}=useSelect(
props,
state,
ref
);// Get props for the button based on the trigger props from useSelectlet{
buttonProps
}=useButton(
triggerProps
);return(<divstyle={{position:'relative',display:'inline-block'}}><div{...labelProps}>{props.label}</div><HiddenSelectstate={state}triggerRef={ref}label={
props.label}name={props.name}/><button{...buttonProps}ref={ref}style={{height:30,fontSize:14}}><span{...valueProps}>{state.selectedItem? state
.selectedItem.rendered:'Select an option'}</span><spanaria-hidden="true"style={{paddingLeft:5}}>
▼
</span></button>{state.isOpen&&(<ListBoxPopup{...menuProps}state={state}/>)}</div>);}functionListBoxPopup({
state,...otherProps
}){let ref =React.useRef();// Get props for the listboxlet{
listBoxProps
}=useListBox({autoFocus:
state.focusStrategy,disallowEmptySelection:true},
state,
ref
);// Handle events that should cause the popup to close,// e.g. blur, clicking outside, or pressing the escape key.let overlayRef =React.useRef();let{
overlayProps
}=useOverlay({onClose:()=>
state.close(),shouldCloseOnBlur:true,isOpen:
state.isOpen,isDismissable:true},
overlayRef
);// Wrap in <FocusScope> so that focus is restored back to the// trigger when the popup is closed. In addition, add hidden// <DismissButton> components at the start and end of the list// to allow screen reader users to dismiss the popup easily.return(<FocusScoperestoreFocus><div{...overlayProps}ref={overlayRef}><DismissButtononDismiss={()=>
state.close()}/><ul{...mergeProps(
listBoxProps,
otherProps
)}ref={ref}style={{position:'absolute',width:'100%',margin:'4px 0 0 0',padding:0,listStyle:'none',border:'1px solid gray',background:'lightgray'}}>{[...state.collection].map((item)=>(<Optionkey={
item.key}item={
item
}state={
state
}/>))}</ul><DismissButtononDismiss={()=>
state.close()}/></div></FocusScope>);}functionOption({
item,
state
}){// Get props for the option elementlet ref =React.useRef();let isDisabled = state.disabledKeys.has(
item.key);let isSelected = state.selectionManager.isSelected(
item.key);let{
optionProps
}=useOption({key: item.key,
isDisabled,
isSelected,shouldSelectOnPressUp:true,shouldFocusOnHover:true},
state,
ref
);// Handle focus events so we can apply highlighted// style to the focused optionlet[
isFocused,
setFocused
]=React.useState(false);let{
focusProps
}=useFocus({onFocusChange: setFocused
});return(<li{...mergeProps(
optionProps,
focusProps
)}ref={ref}style={{background: isSelected
?'blueviolet': isFocused
?'gray':'transparent',color:
isSelected ||
isFocused
?'white':'black',padding:'2px 5px',outline:'none',cursor:'pointer'}}>{item.rendered}</li>);}<Selectlabel="Favorite Color"><Item>Red</Item><Item>Orange</Item><Item>Yellow</Item><Item>Green</Item><Item>Blue</Item><Item>Purple</Item></Select>
useSelect and useListBox handle some aspects of internationalization automatically.
For example, type to select is implemented with an
Intl.Collator
for internationalized string matching. You are responsible for localizing all labels and option
content that is passed into the select.
In right-to-left languages, the select should be mirrored. The arrow should be on the left,
and the selected value should be on the right. In addition, the content of list options should
flip. Ensure that your CSS accounts for this.
Whether user input is required on the input before form submission.
Often paired with the necessityIndicator prop to add a visual indicator to the input.
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.
Provides state management for a select component. Handles building a collection
of items from props, handles the open state for the popup menu, and manages
multiple selection state.
Whether user input is required on the input before form submission.
Often paired with the necessityIndicator prop to add a visual indicator to the input.
Provides the behavior and accessibility implementation for a listbox component.
A listbox displays a list of options and allows a user to select one or more of them.
Props for the description text element inside the option, if any.
A FocusScope manages focus for its descendants. It supports containing focus inside
the scope, restoring focus to the previously focused element on unmount, and auto
focusing children on mount. It also acts as a container for a programmatic focus
management interface that can be used to move focus forward and back in response
to user events.