Tree
A tree represents a hierarchical list of items that might have child items that can be expanded, collapsed, or selected.
Single Select Tree
A tree can be created by using the Tree
component, the useTree
hook to
control the state, and a set of data
. The data
must follow the following
type:
interface TreeItemNode {
itemId: string;
parentId: string | null;
}
type TreeData = Record<string, TreeItemNode>;
The following example will show how to create a single-select tree.
- Folder 1
- Folder 3
"use client";
import { Tree } from "@react-md/core/tree/Tree";
import { useTree } from "@react-md/core/tree/useTree";
import { type ReactElement } from "react";
export default function SingleSelectTreeExample(): ReactElement {
const tree = useTree();
return <Tree {...tree} data={folders} aria-label="Tree" />;
}
export interface Folder {
name: string;
itemId: string;
parentId: string | null;
}
export const folders: Record<string, Folder> = {
"folder-1": {
name: "Folder 1",
itemId: "folder-1",
parentId: null,
},
"folder-2": {
name: "Folder 2",
itemId: "folder-2",
parentId: null,
},
"folder-3": {
name: "Folder 3",
itemId: "folder-3",
parentId: null,
},
"folder-2-1": {
name: "Folder 2 Child 1",
itemId: "folder-2-1",
parentId: "folder-2",
},
"folder-2-2": {
name: "Folder 2 Child 2",
itemId: "folder-2-2",
parentId: "folder-2",
},
"folder-2-3": {
name: "Folder 2 Child 3",
itemId: "folder-2-3",
parentId: "folder-2",
},
"folder-2-2-1": {
name: "Folder 2 Child 2 Child 1",
itemId: "folder-2-2-1",
parentId: "folder-2-2",
},
"folder-2-2-1-1": {
name: "Folder 2 Child 2 Child 1 Child 1",
itemId: "folder-2-2-1-1",
parentId: "folder-2-2-1",
},
};
Multiselect Tree
Multiple items within the tree can be selectable by enabling the multiSelect
option on the useTree
hook.
- Folder 1
- Folder 3
"use client";
import { Tree } from "@react-md/core/tree/Tree";
import { useTree } from "@react-md/core/tree/useTree";
import { type ReactElement } from "react";
export default function MultiSelectTreeExample(): ReactElement {
const tree = useTree({
multiSelect: true,
});
return <Tree {...tree} data={folders} aria-label="Tree" />;
}
export interface Folder {
name: string;
itemId: string;
parentId: string | null;
}
export const folders: Record<string, Folder> = {
"folder-1": {
name: "Folder 1",
itemId: "folder-1",
parentId: null,
},
"folder-2": {
name: "Folder 2",
itemId: "folder-2",
parentId: null,
},
"folder-3": {
name: "Folder 3",
itemId: "folder-3",
parentId: null,
},
"folder-2-1": {
name: "Folder 2 Child 1",
itemId: "folder-2-1",
parentId: "folder-2",
},
"folder-2-2": {
name: "Folder 2 Child 2",
itemId: "folder-2-2",
parentId: "folder-2",
},
"folder-2-3": {
name: "Folder 2 Child 3",
itemId: "folder-2-3",
parentId: "folder-2",
},
"folder-2-2-1": {
name: "Folder 2 Child 2 Child 1",
itemId: "folder-2-2-1",
parentId: "folder-2-2",
},
"folder-2-2-1-1": {
name: "Folder 2 Child 2 Child 1 Child 1",
itemId: "folder-2-2-1-1",
parentId: "folder-2-2-1",
},
};
Customizations
Expansion Mode
When an expandable tree item is clicked, the default behavior is to select it
and toggle the expansion of child items. An alternative approach would be to
only toggle the expansion of child items by clicking on an icon and toggle the
selection by clicking anywhere else in the tree item. To enable this behavior,
set the expansionMode
prop to "manual"
instead of "auto"
(default).
- Folder 1
- Folder 3
"use client";
import { Tree } from "@react-md/core/tree/Tree";
import { useTree } from "@react-md/core/tree/useTree";
import { type ReactElement } from "react";
export default function ExpansionModeExample(): ReactElement {
const tree = useTree();
return (
<Tree {...tree} data={folders} aria-label="Tree" expansionMode="manual" />
);
}
export interface Folder {
name: string;
itemId: string;
parentId: string | null;
}
export const folders: Record<string, Folder> = {
"folder-1": {
name: "Folder 1",
itemId: "folder-1",
parentId: null,
},
"folder-2": {
name: "Folder 2",
itemId: "folder-2",
parentId: null,
},
"folder-3": {
name: "Folder 3",
itemId: "folder-3",
parentId: null,
},
"folder-2-1": {
name: "Folder 2 Child 1",
itemId: "folder-2-1",
parentId: "folder-2",
},
"folder-2-2": {
name: "Folder 2 Child 2",
itemId: "folder-2-2",
parentId: "folder-2",
},
"folder-2-3": {
name: "Folder 2 Child 3",
itemId: "folder-2-3",
parentId: "folder-2",
},
"folder-2-2-1": {
name: "Folder 2 Child 2 Child 1",
itemId: "folder-2-2-1",
parentId: "folder-2-2",
},
"folder-2-2-1-1": {
name: "Folder 2 Child 2 Child 1 Child 1",
itemId: "folder-2-2-1-1",
parentId: "folder-2-2-1",
},
};
Expander Icon
The TreeItem
component will use the
expander icon from the ICON_CONFIG
by
default but can be overridden by setting the expanderIcon
prop on the Tree
component.
Check out the icon rotator custom rotation example to change the rotation while collapsed and expanded.
- Folder 1
- Folder 3
"use client";
import { Tree } from "@react-md/core/tree/Tree";
import { useTree } from "@react-md/core/tree/useTree";
import KeyboardArrowDownIcon from "@react-md/material-icons/KeyboardArrowDownIcon";
import { type ReactElement } from "react";
export default function ExpanderIconExample(): ReactElement {
const tree = useTree();
return (
<Tree
{...tree}
data={folders}
aria-label="Tree"
expanderIcon={<KeyboardArrowDownIcon />}
/>
);
}
export interface Folder {
name: string;
itemId: string;
parentId: string | null;
}
export const folders: Record<string, Folder> = {
"folder-1": {
name: "Folder 1",
itemId: "folder-1",
parentId: null,
},
"folder-2": {
name: "Folder 2",
itemId: "folder-2",
parentId: null,
},
"folder-3": {
name: "Folder 3",
itemId: "folder-3",
parentId: null,
},
"folder-2-1": {
name: "Folder 2 Child 1",
itemId: "folder-2-1",
parentId: "folder-2",
},
"folder-2-2": {
name: "Folder 2 Child 2",
itemId: "folder-2-2",
parentId: "folder-2",
},
"folder-2-3": {
name: "Folder 2 Child 3",
itemId: "folder-2-3",
parentId: "folder-2",
},
"folder-2-2-1": {
name: "Folder 2 Child 2 Child 1",
itemId: "folder-2-2-1",
parentId: "folder-2-2",
},
"folder-2-2-1-1": {
name: "Folder 2 Child 2 Child 1 Child 1",
itemId: "folder-2-2-1-1",
parentId: "folder-2-2-1",
},
};
Expander Icon Position
The expander icon defaults to rendering at the right of each TreeItem
but can
be set to the left by enabling the expanderLeft
prop.
- Folder 1
- Folder 3
"use client";
import { Tree } from "@react-md/core/tree/Tree";
import { type TreeData } from "@react-md/core/tree/types";
import { useTree } from "@react-md/core/tree/useTree";
import FolderIcon from "@react-md/material-icons/FolderIcon";
import { type ReactElement, type ReactNode, useMemo } from "react";
type CustomTreeData = TreeData<Folder & { leftAddon?: ReactNode }>;
export default function ExpanderIconPositionExample(): ReactElement {
const tree = useTree();
const data = useMemo(
() =>
Object.entries(folders).reduce<CustomTreeData>(
(updated, [folderId, folder]) => {
updated[folderId] = {
...folder,
leftAddon: <FolderIcon />,
};
return updated;
},
{},
),
[],
);
return <Tree {...tree} data={data} aria-label="Tree" expanderLeft />;
}
export interface Folder {
name: string;
itemId: string;
parentId: string | null;
}
export const folders: Record<string, Folder> = {
"folder-1": {
name: "Folder 1",
itemId: "folder-1",
parentId: null,
},
"folder-2": {
name: "Folder 2",
itemId: "folder-2",
parentId: null,
},
"folder-3": {
name: "Folder 3",
itemId: "folder-3",
parentId: null,
},
"folder-2-1": {
name: "Folder 2 Child 1",
itemId: "folder-2-1",
parentId: "folder-2",
},
"folder-2-2": {
name: "Folder 2 Child 2",
itemId: "folder-2-2",
parentId: "folder-2",
},
"folder-2-3": {
name: "Folder 2 Child 3",
itemId: "folder-2-3",
parentId: "folder-2",
},
"folder-2-2-1": {
name: "Folder 2 Child 2 Child 1",
itemId: "folder-2-2-1",
parentId: "folder-2-2",
},
"folder-2-2-1-1": {
name: "Folder 2 Child 2 Child 1 Child 1",
itemId: "folder-2-2-1-1",
parentId: "folder-2-2-1",
},
};
Decreasing Spacing
The padding-left
for each tree item increases the deeper within the tree it
is rendered by using:
--rmd-tree-item-padding: calc(
var(--rmd-tree-depth) * var(--rmd-tree-item-padding-incrementor) + var(--rmd-tree-item-padding-base)
);
padding-left: var(--rmd-tree-item-padding):
This means the padding can be configured globally by overriding core.$tree-item-padding-incrementor and core.$tree-item-padding-base or setting the custom properties using core.tree-set-var mixin. The example below will showcase using the mixin.
- Folder 1
- Folder 3
Custom Tree Item Renderer
- Folder 1
- Folder 3