react-d3-tree: a React component for interactive hierarchies on top of the D3 tree layout
:deciduous_tree: React component to create interactive D3 tree graphs
At a glance
- What is it?
- react-d3-tree renders hierarchical data as an interactive SVG tree inside React, handling zoom, pan, node toggling and click events for you while leaving the drawing of each node and link open to CSS or a custom render function. It is a presentational component, not a chart library, and it expects a single rooted object rather than a list of trees.
- Who is it for?
- react-d3-tree suits a React application that needs to show one hierarchy, such as an org chart, a family tree or a file directory, and wants zoom, pan and collapse handled without hand writing SVG plumbing. It is the wrong choice for a dashboard with many small trees, for a graph with cycles or multiple roots, or if you need layouts other than the D3 tree, since that layout is the one the component is built on.
- 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 26 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One root object, and the array form that quietly renders one tree
The data contract is small, and it is the first thing to check against whatever you already have. Tree expects data to be a node object matching the RawNodeDatum interface:
interface RawNodeDatum {
name: string;
attributes?: Record<string, string | number | boolean>;
children?: RawNodeDatum[];
}Name is required and the default node shows it as the main label. Attributes is optional, and the default node lists each key-value pair below the name. Children holds the child nodes, each of which is also a RawNodeDatum.
The rule that catches people is that data also accepts an array, but Tree renders only its first element. If you pass an array of trees expecting a forest, you get one tree and no warning. The same applies to a flat list of nodes, which is a shape many APIs return and which this component cannot consume at all, because there is no parent to child relationship in it.
That is the real constraint of the component. It is built on the tree layout from D3, and a tree is a single rooted hierarchy. A directed graph, a mind map with cross links or a dependency cycle has no representation here.
The quick start in the README shows the intended shape with an org chart, a root named CEO, a child named Manager carrying an attributes object with a department key, and further nesting below that. The name and attributes and children triple is the entire contract, and any data source that can be shaped into it will work.
Install and first render, and what the container div has to do
The package installs from npm:
npm install react-d3-treeThe README states that react-d3-tree supports React 16, 17, 18, and 19, and ships its own TypeScript types, so there is no separate @types package to install.
The first render is a component inside a sized div:
import React from 'react';
import Tree from 'react-d3-tree';
// Each node has a `name`, optional `attributes`, and optional `children`.
const orgChart = {
name: 'CEO',
children: [
{
name: 'Manager',
attributes: { department: 'Production' },
children: [
{
name: 'Foreman',
attributes: { department: 'Fabrication' },
children: [{ name: 'Worker' }],
},
{
name: 'Foreman',
attributes: { department: 'Assembly' },
children: [{ name: 'Worker' }],
},
],
},
],
};
export default function OrgChartTree() {
return (
// `Tree` fills the width and height of its container.
<div id="treeWrapper" style={{ width: '50em', height: '20em' }}>
<Tree data={orgChart} />
</div>
);
}The comment on the wrapper is the instruction that matters. Tree fills the width and height of its container, so the container must have a size. A div with no width and no height gives you a tree with no room, and the symptom is an empty or invisible chart rather than an error. The example uses 50em by 20em for that reason.
The styles in the quick start are inline, which is fine for a demo. In a real application you would normally give the wrapper a class and let CSS decide, and you would size the container from the layout rather than from a fixed em value, because a fixed size is what breaks the moment the same component is used in a responsive panel.
The default import is Tree from react-d3-tree, and the data prop is the only required one. The README points to the TreeProps reference for every prop, its type and its default value, which is where the rest of the surface is documented.
Styling nodes by role with three class name props
By default each node is an SVG circle. The component does not take colours or sizes as props. Instead you pass class names that depend on the node's position in the tree, and you style them in CSS. There are three class name props: rootNodeClassName for the root node, branchNodeClassName for nodes with children, and leafNodeClassName for nodes without children.
The CSS is then ordinary selectors:
/* custom-tree.css */
.node__root > circle {
fill: red;
}
.node__branch > circle {
fill: yellow;
}
.node__leaf > circle {
fill: green;
/* Leaf nodes also get a larger radius. */
r: 40;
}And the JSX passes the names:
import React from 'react';
import Tree from 'react-d3-tree';
import './custom-tree.css';
export default function StyledNodesTree() {
return (
<div id="treeWrapper" style={{ width: '50em', height: '20em' }}>
<Tree
data={data}
rootNodeClassName="node__root"
branchNodeClassName="node__branch"
leafNodeClassName="node__leaf"
/>
</div>
);
}The child combinator in the selectors is doing the work. The class is applied to the node group and the circle is the shape inside it, so you target .node__root > circle rather than .node__root. If you forget that, the fill does not apply and you get default SVG black circles.
There is a design consequence here. Because there is no radius prop, changing node size means either overriding r in CSS, as the leaf example does, or replacing the node entirely with a custom render function. The same applies to shape: a square node is not a prop, it is a different custom node renderer.
This is a deliberate separation of concerns, and it works well when your design system is CSS driven. It is less convenient when you want a node whose size encodes a value, since that pushes you into the custom render function path.
Links are styled through a callback, not a prop
Links work differently from nodes. To add class names to links you pass a function to pathClassFunc. Tree calls it for each link with the link's TreeLinkDatum and the tree's orientation, and adds the returned string to the link's class attribute.
The simplest case returns a fixed string, which gives every link the same classes:
<Tree data={data} pathClassFunc={() => 'custom-link extra-custom-link'} />The useful case inspects the link and chooses per link:
function StyledLinksTree() {
const getDynamicPathClass = ({ source, target }, orientation) => {
// The target has no children, so this link leads to a leaf node.
if (!target.children) {
return 'link__to-leaf';
}
return 'link__to-branch';
}
return <Tree data={data} pathClassFunc={getDynamicPathClass} />;
}The destructured first argument gives you the source and target nodes, so you can branch on properties of either end. The second argument is the orientation, which matters if you draw links differently depending on whether the tree runs left to right or top to bottom.
The important structural point is that pathClassFunc is a function, not a string. That means the link styling is computed per link on every render, and the class list is a string you build rather than a list of names the component applies. If you need several class names, you return a space separated string, as the fixed example shows.
The component also exposes a renderCustomNodeElement prop for replacing the default node, and the README has a section on changing how links are drawn. Those two hooks are the extension points for anything the class name approach cannot express, and they are the reason the component can be used in a design system without fighting it.
Click, hover and the collapse behaviour you have to switch off
Tree accepts six event handlers, and they cover nodes and links separately: onNodeClick, onNodeMouseOver, onNodeMouseOut, onLinkClick, onLinkMouseOver and onLinkMouseOut. There is a seventh, onUpdate, which runs after each zoom or pan and after each update of the tree, such as a node toggle. That is the hook for syncing your own state to what the user just did to the view.
The default click behaviour is worth knowing precisely. A click on a default node's circle expands or collapses the node, and then onNodeClick is called. So the toggle happens first and your handler runs second, and the handler receives a node whose children state has already changed. If your handler is meant to observe the pre-click state, that ordering matters.
To keep nodes from expanding or collapsing, set collapsible to false. onNodeClick still runs. That is the switch to reach for when you want the node click to mean something in your application, such as opening a detail panel, rather than meaning a tree operation.
Centring a node in the view when it is clicked requires passing the container's size to the dimensions prop. Without dimensions the component has no way to know how to centre anything, and the clicked node may end up scrolled out of the visible region, which is a common complaint about tree components that size themselves by content.
The zoom and pan behaviour is built in and driven by onUpdate, so a chart that needs to persist the current viewport across a re-render is relying on state the component owns. The README does not document a controlled viewport prop, so persisting and restoring a pan position is something you would implement around onUpdate rather than through a dedicated API.
A v3 package with a v4 prerelease and a pnpm first toolchain
The release and build metadata tell a story about where this project is. The published version in package.json is 3.7.0-rc.1, and the recent release tags are v4.0.0-next.0 on 2026-09-27, v3.7.0-rc.1 on 2026-09-27 and v3.7.0-rc.0 on 2026-09-22. The last push was on 2026-09-27, so the v4 next tag and the v3 release candidate were cut the same day, which is what a v4 preparation looks like from the outside.
The documentation situation matches. The README links to an API reference labelled v3, and the stable and development docs are described as being built by the same workflow on release and on every push. So if you install the v4 prerelease you are past the published reference documentation, which is a real reason to stay on v3 until the v4 reference is out.
The build system is TypeScript compiled to two module formats. There are separate tsconfig.json and tsconfig.esm.json files, and the build script runs a CommonJS build followed by an ESM build, with a scripts/mark-cjs.js step in between. The package exports map points import at lib/esm/index.js with types at lib/types/index.d.ts, and require at lib/cjs/index.js with types at lib/types-cjs/index.d.ts. The package is marked type as module and ships only the lib directory, so consumers get compiled output and the shipped types rather than the source.
The toolchain is pnpm, not npm, for development. packageManager is pinned to [email protected] and there is a pnpm-lock.yaml and pnpm-workspace.yaml at the top level. The install command in the README still says npm install react-d3-tree, which is fine for consuming the package, but a contributor working from a clone needs pnpm, since the scripts call pnpm run and the workspace file implies a monorepo layout with the demo as a workspace package.
The quality tooling has been modernised. Formatting and linting are oxfmt and oxlint rather than Prettier and ESLint, with.oxfmtrc.json and.oxlintrc.json at the root, and the lint script covers src, scripts, test and the demo. Tests run under vitest with coverage, there is a separate smoke test script, and a check:package script that validates the published artifact. Husky handles git hooks, and the prepare script is written to never fail the install.
The licence is MIT.
Editorial conclusion
react-d3-tree suits a React application that needs to show one hierarchy, such as an org chart, a family tree or a file directory, and wants zoom, pan and collapse handled without hand writing SVG plumbing. It is the wrong choice for a dashboard with many small trees, for a graph with cycles or multiple roots, or if you need layouts other than the D3 tree, since that layout is the one the component is built on. Before adopting, decide whether the v3 line or the v4.0.0-next.0 prerelease is the right target, check that the data you have is a single root object with name, attributes and children, and confirm the styling hooks you need, because the default node is an SVG circle that you colour with class selectors rather than with props.
Frequently asked questions
How do I install react-d3-tree and what React versions does it support?
Run npm install react-d3-tree. The README states that it supports React 16, 17, 18, and 19, and it ships its own TypeScript types, so no separate types package is needed.
What shape does the data prop need in react-d3-tree?
A single node object with a required name, optional attributes and optional children, matching the RawNodeDatum interface. The data prop also accepts an array, but Tree renders only its first element.
How do I stop a node click from collapsing it in react-d3-tree?
Set the collapsible prop to false. The onNodeClick handler still runs, it just no longer expands or collapses the node.
How do I colour nodes differently in react-d3-tree?
Pass rootNodeClassName, branchNodeClassName and leafNodeClassName, then style them in CSS by targeting the circle inside the node group, for example .node__leaf > circle with a fill and an r value. There is no fill or radius prop.
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/bkrem-react-d3-tree)Was this useful?