The useSelect hook provides functionality for building accessible select/dropdown components with full keyboard navigation and ARIA compliance. It's ideal for single-selection scenarios where users choose from a list of options.
Creates a select component with full accessibility features and keyboard navigation.
/**
* Hook for building accessible select/dropdown components
* @param props - Configuration options for the select component
* @returns Object containing state, actions, and prop getters
*/
function useSelect<Item>(props: UseSelectProps<Item>): UseSelectReturnValue<Item>;
interface UseSelectProps<Item> {
/** Array of items to display in the select dropdown */
items: Item[];
/** Function to convert an item to its string representation */
itemToString?: (item: Item | null) => string;
/** Function to generate a unique key for each item */
itemToKey?: (item: Item | null) => any;
/** Function to determine if an item is disabled */
isItemDisabled?: (item: Item, index: number) => boolean;
/** Currently highlighted item index */
highlightedIndex?: number;
/** Initial highlighted index when component mounts */
initialHighlightedIndex?: number;
/** Default highlighted index for uncontrolled usage */
defaultHighlightedIndex?: number;
/** Whether the dropdown is open */
isOpen?: boolean;
/** Initial open state when component mounts */
initialIsOpen?: boolean;
/** Default open state for uncontrolled usage */
defaultIsOpen?: boolean;
/** Currently selected item */
selectedItem?: Item | null;
/** Initial selected item when component mounts */
initialSelectedItem?: Item | null;
/** Default selected item for uncontrolled usage */
defaultSelectedItem?: Item | null;
/** Custom ID for the component */
id?: string;
/** ID for the label element */
labelId?: string;
/** ID for the menu element */
menuId?: string;
/** ID for the toggle button element */
toggleButtonId?: string;
/** Function to generate IDs for menu items */
getItemId?: (index: number) => string;
/** Custom function to handle scrolling items into view */
scrollIntoView?: (node: HTMLElement, menuNode: HTMLElement) => void;
/** Custom state reducer for advanced state management */
stateReducer?: (
state: UseSelectState<Item>,
actionAndChanges: UseSelectStateChangeOptions<Item>
) => Partial<UseSelectState<Item>>;
/** Callback when selected item changes */
onSelectedItemChange?: (changes: UseSelectSelectedItemChange<Item>) => void;
/** Callback when open state changes */
onIsOpenChange?: (changes: UseSelectIsOpenChange<Item>) => void;
/** Callback when highlighted index changes */
onHighlightedIndexChange?: (changes: UseSelectHighlightedIndexChange<Item>) => void;
/** Callback when any state changes */
onStateChange?: (changes: UseSelectStateChange<Item>) => void;
/** Function to generate accessibility status messages */
getA11yStatusMessage?: (options: UseSelectState<Item>) => string;
/** Environment object for SSR/testing scenarios */
environment?: Environment;
}
interface UseSelectReturnValue<Item> {
/** Current highlighted item index */
highlightedIndex: number;
/** Currently selected item */
selectedItem: Item | null;
/** Whether the dropdown menu is open */
isOpen: boolean;
/** Current input value (always empty string for select) */
inputValue: string;
/** Actions for controlling the select programmatically */
reset: () => void;
openMenu: () => void;
closeMenu: () => void;
toggleMenu: () => void;
selectItem: (item: Item | null) => void;
setHighlightedIndex: (index: number) => void;
/** Prop getters for UI elements */
getToggleButtonProps: <Options>(
options?: UseSelectGetToggleButtonPropsOptions & Options,
otherOptions?: GetPropsCommonOptions
) => UseSelectGetToggleButtonReturnValue;
getLabelProps: <Options>(
options?: UseSelectGetLabelPropsOptions & Options
) => UseSelectGetLabelPropsReturnValue;
getMenuProps: <Options>(
options?: UseSelectGetMenuPropsOptions & Options,
otherOptions?: GetPropsCommonOptions
) => UseSelectGetMenuReturnValue;
getItemProps: <Options>(
options: UseSelectGetItemPropsOptions<Item> & Options
) => UseSelectGetItemPropsReturnValue;
}Usage Examples:
import { useSelect } from 'downshift';
// Basic select example
function BasicSelect() {
const items = ['Apple', 'Banana', 'Cherry'];
const { isOpen, selectedItem, getToggleButtonProps, getLabelProps, getMenuProps, highlightedIndex, getItemProps } = useSelect({
items,
});
return (
<div>
<label {...getLabelProps()}>Choose a fruit:</label>
<button {...getToggleButtonProps()}>
{selectedItem || 'Select item'}
</button>
<ul {...getMenuProps()}>
{isOpen &&
items.map((item, index) => (
<li
style={highlightedIndex === index ? { backgroundColor: '#bde4ff' } : {}}
key={`${item}${index}`}
{...getItemProps({ item, index })}
>
{item}
</li>
))}
</ul>
</div>
);
}
// Advanced select with custom itemToString and state reducer
function AdvancedSelect() {
const items = [
{ id: 1, name: 'Apple', category: 'fruit' },
{ id: 2, name: 'Carrot', category: 'vegetable' },
{ id: 3, name: 'Banana', category: 'fruit' },
];
const stateReducer = (state, actionAndChanges) => {
const { changes, type } = actionAndChanges;
switch (type) {
case useSelect.stateChangeTypes.ToggleButtonKeyDownCharacter:
return {
...changes,
// Custom logic for character navigation
};
default:
return changes;
}
};
const { isOpen, selectedItem, getToggleButtonProps, getLabelProps, getMenuProps, highlightedIndex, getItemProps } = useSelect({
items,
itemToString: (item) => (item ? item.name : ''),
stateReducer,
onSelectedItemChange: ({ selectedItem }) => {
console.log('Selected:', selectedItem);
},
});
return (
<div>
<label {...getLabelProps()}>Choose an item:</label>
<button {...getToggleButtonProps()}>
{selectedItem ? selectedItem.name : 'Select item'}
</button>
<ul {...getMenuProps()}>
{isOpen &&
items.map((item, index) => (
<li
style={highlightedIndex === index ? { backgroundColor: '#bde4ff' } : {}}
key={item.id}
{...getItemProps({ item, index })}
>
{item.name} ({item.category})
</li>
))}
</ul>
</div>
);
}The hook returns current state and action functions for programmatic control.
interface UseSelectState<Item> {
/** Index of currently highlighted item (-1 if none) */
highlightedIndex: number;
/** Currently selected item */
selectedItem: Item | null;
/** Whether dropdown menu is open */
isOpen: boolean;
/** Input value (always empty string for select) */
inputValue: string;
}
interface UseSelectActions<Item> {
/** Reset to initial state */
reset: () => void;
/** Open the dropdown menu */
openMenu: () => void;
/** Close the dropdown menu */
closeMenu: () => void;
/** Toggle the dropdown menu open/closed */
toggleMenu: () => void;
/** Select a specific item */
selectItem: (item: Item | null) => void;
/** Set the highlighted index */
setHighlightedIndex: (index: number) => void;
}Prop getters return props to spread onto DOM elements with proper event handlers and accessibility attributes.
interface UseSelectGetToggleButtonPropsOptions extends React.HTMLProps<HTMLElement> {
/** Custom ref key (defaults to 'ref') */
refKey?: string;
/** Event handler for React Native press events */
onPress?: (event: React.BaseSyntheticEvent) => void;
}
interface UseSelectGetToggleButtonReturnValue {
'aria-activedescendant': string;
'aria-controls': string;
'aria-expanded': boolean;
'aria-haspopup': 'listbox';
'aria-labelledby': string | undefined;
id: string;
role: 'combobox';
tabIndex: 0;
ref?: React.RefObject<any>;
onBlur?: React.FocusEventHandler;
onClick?: React.MouseEventHandler;
onPress?: (event: React.BaseSyntheticEvent) => void;
onKeyDown?: React.KeyboardEventHandler;
}
interface UseSelectGetMenuReturnValue {
'aria-labelledby': string | undefined;
role: 'listbox';
id: string;
ref?: React.RefObject<any>;
onMouseLeave: React.MouseEventHandler;
}
interface UseSelectGetItemPropsOptions<Item> {
/** The item this props object is for */
item: Item;
/** Index of the item in the items array */
index?: number;
/** Custom ref key (defaults to 'ref') */
refKey?: string;
}
interface UseSelectGetItemPropsReturnValue {
'aria-disabled': boolean;
'aria-selected': boolean;
id: string;
role: 'option';
ref?: React.RefObject<any>;
onClick?: React.MouseEventHandler;
onMouseDown?: React.MouseEventHandler;
onMouseMove?: React.MouseEventHandler;
onPress?: React.MouseEventHandler;
}Constants for identifying different types of state changes in the state reducer.
enum UseSelectStateChangeTypes {
ToggleButtonClick = '__togglebutton_click__',
ToggleButtonKeyDownArrowDown = '__togglebutton_keydown_arrow_down__',
ToggleButtonKeyDownArrowUp = '__togglebutton_keydown_arrow_up__',
ToggleButtonKeyDownCharacter = '__togglebutton_keydown_character__',
ToggleButtonKeyDownEscape = '__togglebutton_keydown_escape__',
ToggleButtonKeyDownHome = '__togglebutton_keydown_home__',
ToggleButtonKeyDownEnd = '__togglebutton_keydown_end__',
ToggleButtonKeyDownEnter = '__togglebutton_keydown_enter__',
ToggleButtonKeyDownSpaceButton = '__togglebutton_keydown_space_button__',
ToggleButtonKeyDownPageUp = '__togglebutton_keydown_page_up__',
ToggleButtonKeyDownPageDown = '__togglebutton_keydown_page_down__',
ToggleButtonBlur = '__togglebutton_blur__',
MenuMouseLeave = '__menu_mouse_leave__',
ItemMouseMove = '__item_mouse_move__',
ItemClick = '__item_click__',
FunctionToggleMenu = '__function_toggle_menu__',
FunctionOpenMenu = '__function_open_menu__',
FunctionCloseMenu = '__function_close_menu__',
FunctionSetHighlightedIndex = '__function_set_highlighted_index__',
FunctionSelectItem = '__function_select_item__',
FunctionSetInputValue = '__function_set_input_value__',
FunctionReset = '__function_reset__',
}Access via useSelect.stateChangeTypes:
import { useSelect } from 'downshift';
const stateReducer = (state, actionAndChanges) => {
switch (actionAndChanges.type) {
case useSelect.stateChangeTypes.ToggleButtonClick:
// Handle toggle button click
return actionAndChanges.changes;
default:
return actionAndChanges.changes;
}
};