React Stately
React Stately is a JavaScript/TypeScript library developed by Adobe, providing highly accessible and robust state management hooks for complex UI components within the React ecosystem. It is a foundational part of the Adobe React Spectrum and React Aria libraries, focusing on managing component behavior and data without dictating visual presentation. This "headless" approach allows developers to build custom UIs while leveraging battle-tested accessibility and interaction patterns. As of April 2026, the current stable version is 3.46.0. The library follows a frequent release cadence, often with monthly or bi-monthly updates, reflecting ongoing feature development and improvements, especially in coordination with React Aria Components and React Spectrum S2. Its key differentiators include a strong emphasis on WAI-ARIA standards compliance, complex collection management (lists, grids, trees), and sophisticated interaction handling, making it suitable for enterprise-grade applications requiring high accessibility and performance.
Common errors
-
Error: Hooks can only be called inside of the body of a function component.
cause `react-stately` hooks, like all React hooks, must be called directly within a React functional component or a custom hook.fixEnsure `useListState` or other `react-stately` hooks are called at the top level of your functional component, not inside loops, conditions, or nested functions. -
TypeError: state.collection is not iterable
cause This typically occurs if the `items` prop was not provided or was provided incorrectly to `useListState` (or similar hooks), leading to an uninitialized or improperly formed collection object.fixVerify that the `items` array passed to `useListState` is correctly formatted and not `null` or `undefined`. Ensure `getKey` is also provided if items don't have a default `id` or `key` property. -
Warning: Each child in a list should have a unique "key" prop.
cause When rendering items from `state.collection` within a React list, React requires a unique `key` prop for each item to efficiently track changes. This can happen if `getKey` is not specified or returns non-unique values.fixProvide a stable and unique `getKey` function in the `useListState` (or similar hook) options, or explicitly set a `key` prop on your rendered elements using `item.key` or a truly unique identifier from `item.value`. -
Property 'value' does not exist on type 'unknown' (or 'any')
cause This happens when accessing `item.value` from an item retrieved from `state.collection` without providing the correct generic type to the `useListState` hook.fixDefine the generic type for the hook, e.g., `const state = useListState<MyItemType>({ items: ..., ... });`. This ensures `item.value` is correctly typed as `MyItemType`.
Warnings
- gotcha React Stately is a 'headless' state management library, meaning it provides state and logic but no visual UI or accessibility attributes. It is typically used in conjunction with `react-aria` (for accessible DOM properties and interactions) or `@react-spectrum/s2` (for Adobe's Spectrum Design System components). Using React Stately alone requires manual implementation of significant ARIA attributes and event handling for accessibility.
- gotcha The `CollectionBuilder`, `Item`, and `Section` components/utilities, while powerful for declarative collection definition, can be complex. Incorrect usage, especially with dynamic data or deeply nested structures, can lead to incorrect keys, hydration mismatches, or rendering issues.
- gotcha When using `react-stately` hooks with TypeScript, it is crucial to provide correct generic types (e.g., `useListState<MyItemType>`). Failure to do so can result in `any` types, loss of type safety, or type errors when accessing properties from `state.collection` or item values.
- breaking The library primarily uses named exports and is designed for ESM environments. While bundlers usually handle CJS compatibility, direct `require()` statements for specific symbols might not work as expected in certain setups or older Node.js versions.
Install
-
npm install react-stately -
yarn add react-stately -
pnpm add react-stately
Imports
- useListState
const { useListState } = require('react-stately');import { useListState } from 'react-stately'; - useSelectState
import useSelectState from 'react-stately';
import { useSelectState } from 'react-stately'; - useCheckboxGroupState
import { useCheckboxGroupState } from 'react-stately'; - CollectionBuilder
import { CollectionBuilder } from 'react-stately'; - Item
import { Item } from 'react-stately';
Quickstart
import { useListState } from 'react-stately';
import React from 'react';
interface Item {
id: string;
name: string;
}
const initialItems: Item[] = [
{ id: '1', name: 'Apple' },
{ id: '2', name: 'Banana' },
{ id: '3', name: 'Orange' }
];
function MyStatefulList() {
const state = useListState<Item>({
items: initialItems,
selectionMode: 'multiple',
onSelectionChange: (keys) => {
console.log('Current selected keys:', Array.from(keys));
// In a real app, you would update your UI based on this state.
// E.g., render selected items or highlight them.
},
getKey: (item) => item.id,
});
const toggleItem = (id: string) => {
if (state.selectionManager.isSelected(id)) {
state.selectionManager.toggleSelection(id);
} else {
state.selectionManager.addSelection(id);
}
};
return (
<div style={{ padding: '20px', border: '1px solid #ccc', borderRadius: '5px' }}>
<h3>List State Management Example</h3>
<p>Click items to toggle selection:</p>
<ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>
{[...state.collection].map((item) => (
<li
key={item.key}
onClick={() => toggleItem(item.key)}
style={{
padding: '8px',
cursor: 'pointer',
background: state.selectionManager.isSelected(item.key) ? '#e0f7fa' : 'white',
borderBottom: '1px solid #eee'
}}
>
{item.value?.name} {state.selectionManager.isSelected(item.key) && ' (Selected)'}
</li>
))}
</ul>
<p style={{ marginTop: '15px' }}>
Selected IDs: {Array.from(state.selectionManager.selectedKeys).join(', ') || 'None'}
</p>
<button
onClick={() => state.selectionManager.clearSelection()}
style={{ marginTop: '10px', padding: '8px 15px', cursor: 'pointer' }}
>
Clear Selection
</button>
<button
onClick={() => state.selectionManager.selectAll()}
style={{ marginTop: '10px', marginLeft: '10px', padding: '8px 15px', cursor: 'pointer' }}
>
Select All
</button>
</div>
);
}
export default MyStatefulList;