@react-aria/listbox, downshift, react-autocomplete, and react-select are libraries that help developers build accessible, interactive selection UIs like autocomplete fields, dropdowns, and listboxes in React applications. They each provide varying levels of abstraction, customization, and built-in features to handle keyboard navigation, ARIA roles, focus management, and user interactions according to accessibility standards.
When you need to add an autocomplete, dropdown, or listbox to a React app, you quickly realize how much complexity lies beneath a seemingly simple UI: keyboard navigation, screen reader announcements, focus trapping, option filtering, and more. The four packages compared here offer different approaches to solving this problem — from full-featured components to bare-metal hooks. Let’s examine how they differ in practice.
Before diving into comparisons, note that react-autocomplete is deprecated. Its npm page states: "This project is no longer maintained. Please consider using downshift instead." It hasn’t seen meaningful updates since 2018 and doesn’t support modern React patterns like hooks or concurrent rendering. Do not use it in new projects. We include it only for awareness and migration context.
// ❌ Avoid: react-autocomplete (deprecated)
import Autocomplete from 'react-autocomplete';
// This works but lacks modern accessibility fixes and TypeScript support
<Autocomplete
items={items}
renderItem={(item, isHighlighted) => (
<div style={{ background: isHighlighted ? 'lightgray' : 'white' }}>
{item.label}
</div>
)}
value={value}
onChange={setValue}
onSelect={setValue}
/>
Now, let’s compare the three viable options.
@react-aria/listbox: Unstyled ARIA Primitive@react-aria/listbox is part of the React Aria ecosystem from Adobe. It provides low-level hooks that implement ARIA design patterns but render no DOM elements themselves. You must build the entire UI — input, list container, options — and connect them using the hook’s returned props.
import { useListState } from '@react-stately/list';
import { useListBox, useOption } from '@react-aria/listbox';
function ListBox({ options }) {
const state = useListState({ items: options });
const ref = useRef();
const { listBoxProps } = useListBox({ ...state }, state, ref);
return (
<ul {...listBoxProps} ref={ref}>
{[...state.collection].map(item => (
<Option key={item.key} item={item} state={state} />
))}
</ul>
);
}
function Option({ item, state }) {
const ref = useRef();
const { optionProps } = useOption({ key: item.key }, state, ref);
return (
<li {...optionProps} ref={ref}>
{item.rendered}
</li>
);
}
You’ll also need separate hooks like useComboBox or useSelect if you want an input field or trigger button. This modularity is powerful but requires significant setup.
downshift: Headless Interaction Enginedownshift gives you a render-prop or hook-based API that manages selection state, keyboard events, and ARIA attributes, but expects you to render everything. It’s framework-agnostic in spirit and works well with any styling solution.
import { useCombobox } from 'downshift';
function Autocomplete({ items }) {
const {
isOpen,
getMenuProps,
getInputProps,
getToggleButtonProps,
getItemProps,
highlightedIndex
} = useCombobox({
items,
itemToString: item => item?.label || '',
onInputValueChange: ({ inputValue }) => {
// Filter items based on inputValue
}
});
return (
<div>
<input {...getInputProps()} />
<button {...getToggleButtonProps()}>▼</button>
<ul {...getMenuProps()}>
{isOpen &&
items.map((item, index) => (
<li
{...getItemProps({ item, index })}
style={{ backgroundColor: highlightedIndex === index ? 'lightblue' : 'white' }}
>
{item.label}
</li>
))}
</ul>
</div>
);
}
downshift handles tricky parts like closing the menu on blur, arrow key navigation, and ARIA live regions — but you control every pixel.
react-select: Batteries-Included Componentreact-select ships a fully styled, ready-to-use component with sensible defaults. You can customize almost everything via props or theming, but the basic case requires zero extra work.
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' }
];
// ✅ Works out of the box
<Select options={options} />
// Customize with props
<Select
isMulti
isClearable
isSearchable
placeholder="Choose..."
theme={theme => ({
...theme,
borderRadius: 0,
colors: { ...theme.colors, primary: 'black' }
})}
/>
It includes built-in support for async loading, creatable options (CreatableSelect), and even virtualized lists for large datasets.
| Package | Setup Time | Customization Effort | Learning Curve |
|---|---|---|---|
@react-aria/listbox | High | Very High | Steep (requires understanding React Aria’s state/hook split) |
downshift | Medium | High | Moderate (you manage rendering logic) |
react-select | Low | Low–Medium | Shallow (just pass props) |
react-autocomplete | Low | Medium | Shallow — but obsolete |
If you need a quick solution for an internal tool, react-select wins. If you’re building a design system with unique interaction requirements, @react-aria/listbox or downshift give you the foundation without visual opinions.
All three active libraries aim for WCAG compliance, but their approaches differ:
@react-aria/listbox: Implements ARIA 1.2 listbox and combobox patterns exactly as specified. You inherit robust semantics if you follow the docs.downshift: Manages ARIA attributes (aria-activedescendant, aria-expanded, etc.) automatically via its prop getters. Correct usage depends on applying all returned props.react-select: Ships with tested, screen-reader-friendly markup out of the box. Handles edge cases like announcement of selected items and live region updates.However, accessibility is only as good as your implementation. With @react-aria/listbox or downshift, forgetting to spread {...optionProps} breaks keyboard nav. With react-select, you’re safe by default — unless you override critical props.
@react-aria/listbox: Zero styles. You bring your own CSS, CSS-in-JS, or utility classes.downshift: Also zero styles. Entirely dependent on your rendering.react-select: Comes with default styles (via Emotion) but supports deep customization through the styles and theme props, or by replacing components entirely via the components prop.// react-select: Replace only the Option component
<Select
components={{
Option: ({ children, ...props }) => (
<div {...props.innerProps} className="my-custom-option">
{children}
</div>
)
}}
/>
| Feature | @react-aria/listbox | downshift | react-select |
|---|---|---|---|
| Async options | ❌ (you implement) | ❌ (you implement) | ✅ (loadOptions prop) |
| Multi-select | ❌ (use useMultipleSelection) | ✅ (with extra logic) | ✅ (isMulti prop) |
| Creatable options | ❌ | ✅ (manual) | ✅ (CreatableSelect) |
| Virtualized lists | ❌ | ❌ | ✅ (via react-window integration) |
| Built-in filtering | ❌ | ❌ | ✅ (customizable via filterOption) |
| TypeScript support | ✅ | ✅ | ✅ |
@react-aria/listbox when:downshift when:react-select when:react-autocomplete in new codebases.These tools exist on a spectrum from foundation (@react-aria/listbox, downshift) to product (react-select). Choose based on whether your priority is control or velocity. And always — always — test with a keyboard and screen reader, no matter which library you pick.
Choose @react-aria/listbox if you're already using or planning to adopt the full React Aria suite and need a low-level, unstyled component that strictly follows ARIA patterns. It gives you complete control over rendering and styling but requires you to implement layout, filtering, and state management yourself. Best suited for design systems or teams that prioritize strict accessibility compliance and custom UIs.
Choose downshift if you want a lightweight, headless library that handles complex interaction logic (like keyboard navigation and ARIA attributes) while leaving all rendering and styling up to you. It’s ideal when you need maximum flexibility without opinionated markup or styles, and you’re comfortable wiring up your own input, list, and item components.
Do not choose react-autocomplete for new projects — it is officially deprecated as noted on its npm page. The package hasn’t been updated in years and lacks modern React features, accessibility improvements, and active maintenance. Migrate existing usage to alternatives like downshift or react-select.
Choose react-select if you need a fully-featured, production-ready select component with built-in support for async options, multi-select, creatable options, extensive theming, and accessibility. It’s best when you want to ship quickly with minimal custom implementation, especially in admin panels, forms, or internal tools where consistent UX outweighs pixel-perfect design control.
This package is part of react-spectrum. See the repo for more details.