react-accessible-treeview, react-sortable-tree, and react-treebeard are React libraries designed to render hierarchical data as interactive tree structures in web applications. Each provides a different approach to visualizing and interacting with nested data, such as file systems, organizational charts, or category trees. While all three aim to solve similar problems, they differ significantly in their focus on accessibility, drag-and-drop capabilities, performance, and API design.
When you need to display hierarchical data in a React app — like folders, org charts, or nested categories — picking the right tree component library can make or break your user experience. react-accessible-treeview, react-sortable-tree, and react-treebeard each take a different path. Let’s compare them based on real engineering concerns: accessibility, maintenance status, interaction model, and performance.
Before diving into features, check if the library is still alive.
react-sortable-tree is deprecated. Both its npm page and GitHub repo state it’s archived and no longer maintained. Do not use it in new projects. It hasn’t kept up with React 18+ changes, and community forks are fragmented.
react-accessible-treeview and react-treebeard are actively maintained, with recent updates and responsive issue tracking.
🛑 If you’re maintaining a legacy app using
react-sortable-tree, plan a migration. For new work, skip it entirely.
How well does each library support screen readers and keyboard navigation?
react-accessible-treeview is built from the ground up for accessibility. It follows WAI-ARIA practices for tree views, including proper roles (tree, treeitem, group), aria-expanded, aria-selected, and full keyboard support (arrow keys, Enter, Space).
// react-accessible-treeview: Accessible by default
import { TreeView, flattenTree } from 'react-accessible-treeview';
const data = [
{ id: 1, name: 'Documents', children: [
{ id: 2, name: 'Report.pdf' }
]}
];
<TreeView
data={flattenTree(data)}
aria-label="File explorer"
/>
react-treebeard does not implement ARIA patterns. You’ll need to add roles, labels, and keyboard handlers yourself if accessibility is required.
// react-treebeard: No built-in ARIA
import { Treebeard } from 'react-treebeard';
// You must manually add accessibility attributes
// (not shown in official examples)
<Treebeard data={data} />
react-sortable-tree had partial accessibility support but never achieved full compliance. Given its deprecated status, this is moot.
How do you control the tree and respond to user actions?
react-accessible-treeview uses a reducer-based state management model. You provide an initial state and handle actions like TOGGLE_NODE or SET_EXPANDED via a custom reducer. This gives fine-grained control but requires more setup.
// react-accessible-treeview: External state control
const [state, dispatch] = useReducer(treeReducer, {
expandedIds: [],
selectedIds: []
});
<TreeView
data={flattenedData}
{...state}
onNodeToggle={(id) => dispatch({ type: 'TOGGLE_NODE', payload: id })}
/>
react-treebeard uses a functional, immutable approach. You pass the current data and an onToggle callback. When a node is toggled, you return a new data object with updated toggled flags.
// react-treebeard: Immutable updates
const [data, setData] = useState(initialData);
const onToggle = (node, toggled) => {
const newData = toggleNode(data, node, toggled);
setData(newData);
};
<Treebeard data={data} onToggle={onToggle} />
react-sortable-tree used a more imperative style with props like onChange that returned the entire modified tree. Again, this is historical context only.
Need to reorder nodes by dragging?
react-accessible-treeview does not support drag-and-drop. You’d need to integrate a library like react-dnd yourself.
react-treebeard also does not include drag-and-drop.
react-sortable-tree was known for its robust drag-and-drop, but again — it’s deprecated.
💡 If drag-and-drop is essential, consider alternatives like
@dnd-kitwith a custom tree implementation, or evaluate whether your use case truly requires reordering.
How do they handle hundreds or thousands of nodes?
react-treebeard uses a recursive rendering model without virtualization. Performance degrades with very large trees, but it’s acceptable for moderate sizes (<500 nodes).
react-accessible-treeview renders only visible nodes by default (it flattens the tree internally and uses efficient mapping). This makes it more scalable for larger datasets while maintaining accessibility.
Neither library implements windowing (like react-window), so for extremely large trees (>10k nodes), you’d need a custom solution.
react-accessible-treeview provides minimal default styles and expects you to customize via CSS classes or styled-components. It exposes class names like .tree-node and .tree-node__label.
react-treebeard includes a default theme with smooth animations and supports custom themes via a theme prop object.
// react-treebeard: Custom theme
const customTheme = {
tree: { base: { listStyle: 'none' } },
node: { link: { color: 'blue' } }
};
<Treebeard data={data} theme={customTheme} />
| Concern | react-accessible-treeview | react-treebeard | react-sortable-tree |
|---|---|---|---|
| Maintenance | ✅ Actively maintained | ✅ Actively maintained | ❌ Deprecated |
| Accessibility | ✅ Full ARIA compliance | ❌ None built-in | ⚠️ Partial (historical) |
| Drag-and-Drop | ❌ Not supported | ❌ Not supported | ✅ Supported (but deprecated) |
| State Management | 🔧 Reducer-based (explicit) | 🔄 Immutable updates | 📜 Imperative (historical) |
| Performance | ✅ Efficient for large trees | ⚠️ Good for small/medium trees | ⚠️ Unknown (unmaintained) |
| Styling | 🎨 CSS classes | 🎨 Theme object | 🎨 CSS classes |
react-accessible-treeview. The extra setup pays off in compliance and usability.react-treebeard.react-sortable-tree entirely in new codebases. If you’re stuck with it, migrate to one of the other two or build a custom solution using modern primitives.Remember: a tree component seems simple until you need keyboard nav, screen reader support, or to handle 1,000 nodes. Pick the tool that matches your project’s real constraints — not just the demo screenshots.
Choose react-accessible-treeview if you need a fully accessible tree component that meets WCAG standards and supports keyboard navigation out of the box. It’s ideal for enterprise applications where accessibility compliance is non-negotiable, and you’re willing to manage state externally using a reducer pattern. Avoid it if you need built-in drag-and-drop or visual customization beyond basic styling.
Do not choose react-sortable-tree for new projects — it is officially deprecated and no longer maintained. The npm page and GitHub repository clearly state it has been archived. While it once offered powerful drag-and-drop reordering and a rich feature set, its unmaintained status means it lacks compatibility with modern React versions (especially concurrent features) and may contain unpatched bugs or security issues.
Choose react-treebeard if you prioritize performance with large datasets and prefer a minimal, functional API that encourages immutability. Its animated transitions and clean design work well for read-heavy trees like documentation navigators or static hierarchies. However, avoid it if you need native drag-and-drop, complex node interactions, or full accessibility support, as these are not part of its core offering.
We're looking to give this repository to a new owner. Please see issue https://github.com/dgreene1/react-accessible-treeview/issues/222 to inquire about being the new owner.
A react component that implements the treeview pattern as described by the WAI-ARIA Authoring Practices.
| Prop name | Type | Default value | Description |
|---|---|---|---|
data | array[node] | required | Tree data |
nodeRenderer | func | required | Render prop for the node (see below for more details) |
onSelect | func | noop | Function called when a node changes its selected state |
onNodeSelect | func | noop | Function called when a node was manually selected/deselected |
onExpand | func | noop | Function called when a node changes its expanded state |
className | string | "" | className to add to the outermost dom element, al ul with role = "tree" |
multiSelect | bool | false | Allows multiple nodes to be selected |
propagateSelect | bool | false | If set to true, selecting a node will also select its descendants |
propagateSelectUpwards | bool | false | If set to true, selecting a node will update the state of its parent (e.g. a parent node in a checkbox will be automatically selected if all of its children are selected |
propagateCollapse | bool | false | If set to true, collapsing a node will also collapse its descendants |
expandOnKeyboardSelect | bool | false | Selecting a node with a keyboard (using Space or Enter) will also toggle its expanded state |
togglableSelect | bool | false | Whether the selected state is togglable |
defaultSelectedIds | array | [] | Array with the ids of the default selected nodes. Unused if selectedIds is provided. |
defaultExpandedIds | array | [] | Array with the ids of the default expanded nodes. Unused if expandedIds is provided. |
defaultDisabledIds | array | [] | Array with the ids of the default disabled nodes |
selectedIds | array | [] | (Controlled) Array with the ids that should be selected |
expandedIds | array | [] | (Controlled) Array with the ids of branch node that should be expanded |
clickAction | enum | SELECT | Action to perform on click. One of: EXCLUSIVE_SELECT, FOCUS, SELECT |
onBlur | func | noop | Custom onBlur event that is triggered when focusing out of the component as a whole (moving focus between the nodes won't trigger it). |
An array of nodes. Nodes are objects with the following structure:
| Property | Type | Default | Description |
|---|---|---|---|
id | number or string | required | A nonnegative integer or string that uniquely identifies the node |
name | string | required | Used to match on key press |
children | array[id] | required | An array with the ids of the children nodes. |
parent | id | required | The parent of the node. null for the root node |
isBranch | boolean | optional | Used to indicated whether a node is branch to be able load async data onExpand, default is false |
metadata | object | optional | Used to add metadata into node object. We do not currently support metadata that is a nested object |
The item with parent:null of the array represents the root node and won't be displayed.
Example:
const data = [
{ name: "", children: [1, 4, 9, 10, 11], id: 0, parent: null },
{ name: "src", children: [2, 3], id: 1, parent: 0 },
{ name: "index.js", id: 2, parent: 1 },
{ name: "styles.css", id: 3, parent: 1 },
{ name: "node_modules", children: [5, 7], id: 4, parent: 0 },
{ name: "react-accessible-treeview", children: [6], id: 5, parent: 4 },
{ name: "bundle.js", id: 6, parent: 5 },
{ name: "react", children: [8], id: 7, parent: 4 },
{ name: "bundle.js", id: 8, parent: 7 },
{ name: ".npmignore", id: 9, parent: 0 },
{ name: "package.json", id: 10, parent: 0 },
{ name: "webpack.config.js", id: 11, parent: 0 },
];
The array can also be generated from a nested object using the flattenTree helper (see the examples below). flattenTree preserves metadata.
Data supports non-sequential ids provided by user.
| Property | Type | Description |
|---|---|---|
element | object | The object that represents the rendered node |
getNodeProps | function | A function which gives back the props to pass to the node |
isBranch | bool | Whether the rendered node is a branch node |
isSelected | bool | Whether the rendered node is selected |
isHalfSelected | bool or undefined | If the node is a branch node, whether it is half-selected, else undefined |
isExpanded | bool or undefined | If the node is a branch node, whether it is expanded, else undefined |
isDisabled | bool | Whether the rendered node is disabled |
level | number | A positive integer that corresponds to the aria-level attribute |
setsize | number | A positive integer that corresponds to the aria-setsize attribute |
posinset | number | A positive integer that corresponds to the aria-posinset attribute |
handleSelect | function | Function to assign to the onClick event handler of the element(s) that will toggle the selected state |
handleExpand | function | Function to assign to the onClick event handler of the element(s) that will toggle the expanded state |
dispatch | function | Function to dispatch actions |
treeState | object | state of the treeview |
onSelect({element, isBranch, isExpanded, isSelected, isHalfSelected, isDisabled, treeState })
Note: the function uses the state after the selection.onNodeSelect({element, isBranch, isSelected, treeState })
Note: the function uses the state right after the selection before propagation.onExpand({element, isExpanded, isSelected, isHalfSelected, isDisabled, treeState})
Note: the function uses the state after the expansion.onLoadData({element, isExpanded, isSelected, isHalfSelected, isDisabled, treeState})
Note: the function uses the state after inital data is loaded and on expansion.
Follows the same conventions described in https://www.w3.org/TR/wai-aria-practices/examples/treeview/treeview-1/treeview-1b.html and https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-22.
| Key | Function |
|---|---|
Enter or Space | Updates the selected node to the current node and triggers onSelect |
Down Arrow |
|
Up arrow |
|
Right Arrow |
|
Left Arrow |
|
Home | Moves focus to first node without opening or closing a node. |
End | Moves focus to the last node that can be focused without expanding any nodes that are closed. |
a-z, A-Z |
|
* (asterisk) |
|
Shift + Down Arrow | Moves focus to and toggles the selection state of the next node. |
Shift + Up Arrow | Moves focus to and toggles the selection state of the previous node. |
Ctrl + A | Selects all nodes in the tree. If all nodes are selected, unselects all nodes. |
| Key | Function |
|---|---|
Click | Toggles parent nodes and also performs one of clickActions = SELECT, EXCLUSIVE_SELECT, FOCUS |
Ctrl+Click | If multiselect is set to true, selects the node without dropping the current selected ones. If false, it selects the clicked node. Doesn't toggle parents. |
Shift+Click | If multiselect is set to true, selects from the node without dropping the current selected ones. If false, it focus the clicked node. Doesn't toggle parents. |
| Variant | Function |
|---|---|
SELECT | Selects the clicked node (default). |
EXCLUSIVE_SELECT | Selects the clicked node and deselects the rest. |
FOCUS | Focuses the clicked node |
The internal state of the component.
| Property | Type | Default | Description |
|---|---|---|---|
| selectedIds | Set | new Set(defaultSelectedIds) | Set of the ids of the selected nodes |
| controlledIds | Set | new Set(controlledSelectedIds) | Set of the ids of the nodes selected programmatically |
| tabbableId | number | data[0].children[0] | Id of the node with tabindex = 0 |
| isFocused | bool | false | Whether the tree has focus |
| expandedIds | Set | new Set(defaultExpandedIds) | Set of the ids of the expanded nodes |
| halfSelectedIds | Set | new Set() | Set of the ids of the selected nodes |
| lastUserSelect | number | data[0].children[0] | Last selection made directly by the user |
| lastInteractedWith | number or null | null | Last node interacted with |
| lastManuallyToggled | number or null | null | Last node that was manually selected/deselected |
| disabledIds | Set | new Set(defaultDisabledIds) | Set of the ids of the selected nodes |