🏎 A set of primitives to build simple, flexible, WAI-ARIA compliant React autocomplete, combobox or select dropdown components.
Downshift is a library that provides low-level primitives for building accessible autocomplete, combobox, select, and dropdown components in React. Rather than shipping pre-styled components, it offers React hooks like useCombobox, useSelect, and useMultipleSelection that handle the complex state management, keyboard interactions, and ARIA compliance required for these patterns. You maintain full control over rendering and styling while downshift manages the interaction logic.
The library solves a specific problem: building input components with dynamic suggestions or selections is deceptively complex. You need to handle keyboard navigation (arrow keys, Enter, Escape, Home, End), screen reader announcements, focus management, mouse interactions, touch events, and edge cases like rapid typing or async data fetching. Downshift encapsulates these behaviors into prop getters—functions that return the exact props, event handlers, and refs your DOM elements need.
With over 2.5 million weekly downloads, downshift powers autocomplete and select interfaces across the React ecosystem. It's particularly valuable for design systems and component libraries that need accessible primitives without prescriptive styling. The hooks-based API (introduced after the original render prop pattern) integrates cleanly with modern React codebases, and the state reducer pattern allows fine-grained control over behavior when defaults don't fit your use case.
Downshift is maintained as part of the broader effort to provide headless, accessible UI primitives. It focuses solely on interaction logic—you're responsible for positioning (often via Popper.js), styling, and rendering. This trade-off makes it lighter than full-featured libraries like React-Select but requires more upfront implementation work.
import { useCombobox } from 'downshift';
import { useState } from 'react';
const fruits = [
'Apple', 'Banana', 'Cherry', 'Dragonfruit', 'Elderberry',
'Fig', 'Grape', 'Honeydew', 'Kiwi', 'Lemon'
];
function FruitAutocomplete() {
const [items, setItems] = useState(fruits);
const {
isOpen,
getInputProps,
getMenuProps,
getItemProps,
highlightedIndex,
selectedItem
} = useCombobox({
items,
onInputValueChange: ({ inputValue }) => {
setItems(
fruits.filter(fruit =>
fruit.toLowerCase().includes(inputValue?.toLowerCase() || '')
)
);
},
onSelectedItemChange: ({ selectedItem }) => {
console.log('Selected:', selectedItem);
}
});
return (
<div style={{ position: 'relative', width: '300px' }}>
<label {...getInputProps().ref.current ? {} : {}} style={{ display: 'block', marginBottom: '4px' }}>
Choose a fruit:
</label>
<input
{...getInputProps()}
style={{
width: '100%',
padding: '8px',
border: '1px solid #ccc',
borderRadius: '4px'
}}
placeholder="Type to search..."
/>
<ul
{...getMenuProps()}
style={{
position: 'absolute',
width: '100%',
margin: 0,
padding: 0,
listStyle: 'none',
background: 'white',
border: isOpen && items.length ? '1px solid #ccc' : 'none',
borderRadius: '4px',
marginTop: '4px',
maxHeight: '200px',
overflowY: 'auto',
zIndex: 1000
}}
>
{isOpen &&
items.map((item, index) => (
<li
key={item}
{...getItemProps({ item, index })}
style={{
padding: '8px',
backgroundColor: highlightedIndex === index ? '#e0e0e0' : 'white',
fontWeight: selectedItem === item ? 'bold' : 'normal',
cursor: 'pointer'
}}
>
{item}
</li>
))}
</ul>
</div>
);
}Searchable product filters: An e-commerce site needs a category selector with type-ahead filtering. Using useCombobox, the input filters a list of categories as users type, highlights matches with arrow keys, and allows selection via Enter or click. The component fetches suggestions from an API with debouncing, showing loading states through downshift's exposed isOpen and custom loading flags.
Multi-select tag input: A project management tool needs a task assignment field where users select multiple team members. Combining useMultipleSelection with useCombobox lets users type to filter names, select via Enter, and see selected members as removable chips below the input. Downshift handles removing selected items from the dropdown list and managing focus when chips are deleted.
Accessible country selector: A form requires a country dropdown that works with keyboard and screen readers. useSelect provides the ARIA relationships, focus management, and keyboard navigation (type-to-search, arrow navigation) without writing custom event handlers. The rendered button and listbox receive props from getToggleButtonProps() and getMenuProps(), ensuring WCAG 2.1 compliance.
Command palette: A documentation site implements a Cmd+K search interface that filters pages and actions. useCombobox manages the input state, highlighted index, and selection while the component handles fuzzy matching against a local index. The state reducer customizes behavior—preventing menu close on certain selections to support chaining commands.
Async location autocomplete: A delivery app autocompletes addresses using Google Places API. As users type, debounced requests fetch suggestions. Downshift's onInputValueChange triggers fetches, and items updates dynamically. The library handles race conditions through proper state management, ensuring the highlighted item stays valid as results change.
A set of completely unstyled, fully accessible UI components for React, designed to integrate beautifully with Tailwind CSS.
Spectrum UI components in React
A Select control built with and for ReactJS
npm install downshiftpnpm add downshiftbun add downshift