React Arborist: a virtualized React tree view with drag and drop
The complete tree view component for React
At a glance
- What is it?
- React Arborist is an MIT-licensed TypeScript tree component for React, aimed at file explorers, sidebars and layer panels. Its controlled mode has a documented index quirk that needs the adjustMoveIndex helper.
- Who is it for?
- Adopt React Arborist if you are building a file explorer, sidebar or layer panel in React and want virtualization, drag and drop, filtering and inline renaming from one component instead of assembling them yourself. Do not adopt it if your tree is small and static, or if you are not on React, since the library targets the React ecosystem specifically.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 75 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 5, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What React Arborist replaces in a React app
A tree view looks simple until you build one. You need rows that stay cheap when the data set is large, a drag handle that knows where a row will land, keyboard navigation that matches what screen readers expect, and a rename field that appears in place. React Arborist packages those pieces into a single component. The README frames the target directly: it "provides the React ecosystem with a complete solution to build the equivalent of a VSCode sidebar, Mac Finder, Windows Explorer, or Sketch/Figma layers panel."
The audience is React developers who need that sidebar, not people who need a general-purpose data grid. The listed feature set is specific to tree interaction: drag and drop sorting, open and close folders, inline renaming, virtualized rendering, custom styling, keyboard navigation, aria attributes, tree filtering, selection synchronization, callbacks such as onScroll, onActivate and onSelect, and controlled or uncontrolled trees. If your requirement is a flat list of a few hundred rows, this is more component than you need.
How the Tree component is put together
The component takes your data as an array of objects with an id and, optionally, a children array. Two props decide who owns that data. Passing initialData makes the tree uncontrolled, and the README states that "Create, move, rename, and delete will be handled internally." Passing data instead makes it controlled, and you supply onCreate, onRename, onMove and onDelete handlers.
Rendering is virtualized. You give the tree width, height, rowHeight, indent and overscanCount, and it renders the visible window rather than every node. You also supply the row markup as a child function. The example Node component receives node, style and dragHandle, and the README notes that the node instance "can do many things." Attaching dragHandle as a ref is what makes that row draggable, so a custom row that forgets the ref loses drag and drop.
Data shape is not fixed. The idAccessor and childrenAccessor props let you point at different field names, either as a string property name or as a function that receives the data. Filtering works through a searchTerm prop plus an optional searchMatch function. The README describes the default matcher as deliberately loose: it JSON-stringifies the data's values and looks for the term anywhere in the result, so a search can hit the keys of a nested object. That is convenient for demos and sloppy for production, which is why the README tells you to pass searchMatch when you want specific fields.
Installing React Arborist and rendering a first tree
The README gives two package-manager commands and nothing else in the way of setup. Pick one:
yarn add react-arboristnpm install react-arboristWith the package installed, the smallest useful tree is a data array and one component. The README's own sample data is a Gmail-style sidebar:
const data = [
{ id: "1", name: "Unread" },
{ id: "2", name: "Threads" },
{
id: "3",
name: "Chat Rooms",
children: [
{ id: "c1", name: "General" },
{ id: "c2", name: "Random" },
{ id: "c3", name: "Open Source Projects" },
],
},
];Rendering it takes one import and one element. Because initialData is used, this tree is uncontrolled and edits stay inside the component:
import { Tree } from 'react-arborist';
function App() {
return <Tree initialData={data} />;
}You should see a list of rows with expandable folders. The next step in the README is a custom Node component, where you set your own dimensions and row markup:
function App() {
return (
<Tree initialData={data} openByDefault={false} width={600} height={1000} indent={24} rowHeight={36}>
{Node}
</Tree>
);
}
function Node({ node, style, dragHandle }) {
return (
<div style={style} ref={dragHandle}>
{node.isLeaf ? "🍁" : "🗀"}
{node.data.name}
</div>
);
}The style object carries the positioning the virtualizer expects, and dragHandle must be attached to the element that should start a drag. Beyond this, the README points to a demos site and CodeSandbox links rather than a written API walkthrough.
The onMove index is the sharp edge
The README devotes a subsection to the index passed to onMove, and it is the part most likely to bite a controlled tree. The index is a pre-removal slot: it counts positions in the destination parent's child list as shown on screen, with the dragged rows still in place.
If your handler removes the dragged rows before inserting them, the natural way to reorder an array, then every dragged row that started before the drop slot shifts your target one place to the left. The README gives the symptom plainly: dragging a row to just below itself would jump it past its neighbor. That behaviour is tracked as issue #247.
SimpleTree and useSimpleTree insert before removing, so they need no adjustment. If you manage the data yourself, the library exports adjustMoveIndex. The README's example passes the destination parent's current child ids in display order, and notes that ids which are not among them, meaning rows moving in from another parent, do not shift anything. The practical consequence is that you cannot copy a generic array-reorder snippet into onMove and expect it to be correct. Either use the simple tree helpers or call adjustMoveIndex.
Controlled state, selection sync and the Tree API
Three integration points decide how much of the component you actually use. The first is data ownership. A controlled tree means writing onCreate, onRename, onMove and onDelete handlers and keeping your own state in sync, which is where the onMove index issue becomes your problem.
The second is selection. The README describes a common pattern: something opens elsewhere in the app and the tree should reflect it. Passing an id to the selection prop selects and scrolls to that node whenever the id changes. That is a one-prop answer to a problem that otherwise needs a ref and an effect.
The third is the Tree API instance, reached by giving a ref to the tree. The README's example calls tree.selectAll() inside a useEffect. The API reference is external to the README, so the full method list is not visible from the repository front page. If your design depends on imperative control beyond selectAll, check that reference before committing, because the README only gestures at it.
React Arborist compared with React Complex Tree
React Complex Tree is the alternative that comes up most often in searches around this project, and the difference is one of scope rather than quality. React Arborist presents itself as a complete tree view: the README's feature list treats drag and drop, renaming, filtering, keyboard navigation and aria attributes as built-in behaviour you configure with props.
That makes the component opinionated. You get a Node render function and a style object, and the interaction model is already decided. A library that leaves more of the interaction to the caller gives you room to build a different model, at the cost of writing the parts React Arborist ships. The trade-off shows up in the filtering default, where the loose JSON-stringify matcher is convenient but needs replacing for real search, and in the onMove index, where the abstraction leaks and you must reach for adjustMoveIndex.
There is no benchmark data in the repository comparing the two, so the choice should rest on which interaction model matches your product, not on a performance claim.
Maintenance, licence and upgrade surface
React Arborist is MIT licensed, so you can use it in commercial and closed-source applications. The repository carries a LICENSE file at the top level. MIT does not cover the dependencies you install alongside it, and the repository does not document a support commitment, so treat the licence as permission rather than a maintenance promise.
The last push to the default branch was on 2026-07-25, and the most recent release listed is v3.16.0 on the same date, following v3.15.1 and v3.15.0 earlier that month. The repository is not archived. That is a recent enough history to suggest the project is being worked on, but the repository does not describe a release cadence or a deprecation policy, so upgrade cost is hard to predict from the project alone.
What is visible is the tooling. The monorepo uses Yarn 4.0.2 with workspaces, and the root package.json defines build, test, lint and format scripts, with oxlint and oxfmt as the lint and format tools. A CHANGELOG.md and a .changes directory sit at the top level, which is where you would look for breaking changes between the v3.15 and v3.16 lines. The README does not document a migration path between major versions.
Where React Arborist is the wrong choice
The clearest failure mode is a tree that is not really a tree. If you need columns, sorting by header, inline cell editing or pagination, you want a data grid, and the Node render function here will fight you.
The second is a small static hierarchy. Virtualization, drag handles and the controlled or uncontrolled split all exist to manage interaction at size. A nested list of twenty items rendered with a plain map has fewer moving parts and no onMove index to reason about.
The third is the controlled path specifically. If your state management removes rows before inserting them, and you do not want to adopt adjustMoveIndex or the SimpleTree helpers, the drop behaviour documented in issue #247 will reproduce in your app. That is a design constraint, not a bug report to wait out.
Finally, the README is example-driven. It shows demos and CodeSandbox links, and it names a Tree API reference without reproducing it. Teams that need a written specification for every prop before adopting will find the front page thin.
Editorial conclusion
Adopt React Arborist if you are building a file explorer, sidebar or layer panel in React and want virtualization, drag and drop, filtering and inline renaming from one component instead of assembling them yourself. Do not adopt it if your tree is small and static, or if you are not on React, since the library targets the React ecosystem specifically. Before committing, check the README's onMove index section and decide whether you will use SimpleTree, useSimpleTree or the exported adjustMoveIndex helper, because a controlled tree that removes rows before inserting them will misplace drops.
Frequently asked questions
How do I install React Arborist?
The README gives two commands: yarn add react-arborist or npm install react-arborist. There is no separate setup step documented beyond installing the package.
Does React Arborist support drag and drop?
Yes. Drag and drop sorting is listed as a feature, and the custom Node example attaches the dragHandle ref to the row element to make it draggable.
What is the difference between the data and initialData props on React Arborist?
initialData makes the tree uncontrolled, and the README states that create, move, rename and delete are handled internally. data makes it controlled, and you handle those changes yourself through onCreate, onRename, onMove and onDelete.
Why does my React Arborist drop land in the wrong position?
The index passed to onMove is a pre-removal slot, counting positions with the dragged rows still in place. If your handler removes rows before inserting them, the target shifts left; the README points to adjustMoveIndex, or to SimpleTree and useSimpleTree, which insert before removing.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jameskerr-react-arborist)