Tree
Install: npm install @mantine/core@9.7.1 @mantine/hooks@9.7.1
Version and compatibility
Pinned Mantine Core 9.7.1 requires React and React DOM ^19.2.0 and matching @mantine/hooks@9.7.1, from the installed npm package's peerDependencies. Do not force it into React 18 or another incompatible version. Explain upgrade impact and agree on a plan with the project owner; do not hide conflicts with --force/--legacy-peer-deps. The native Provider enables theme.respectReducedMotion and uses forceColorScheme for light/dark.
Use case
Browse a hierarchical data structure.
Native props used by this example
These props are extracted from the local typechecked JSX and grouped by their owning native component. This is not an exhaustive prop list; see pinned source signatures and inheritance below.
| Component prop | Explanation |
|---|---|
Tree.data | Local options/nodes; keep unique values and native schema. |
Interaction and state checks
Current scenarios: Default. Check actual behavior after selecting supported disabled/error/read-only scenarios; components without those native states do not receive invented universal props. Verify keyboard focus and exits, then connect changed values through the native callback signature.
Business integration boundary
Keep node values unique and use the native tree state API for controlled expansion/selection. Local nodes are not filesystem permissions.
Host setup
Install pinned @mantine/core and @mantine/hooks, import @mantine/core/styles.css at the entry and use MantineProvider. The example's theme/variant are JGuang-authored host props, not universal native component APIs; forceColorScheme synchronizes the app theme. Native implementation source is a reading reference: use npm exports rather than copying internal engines. This batch covers Core, not dates/charts/forms extension packages. Detailed upstream prose remains English.
Runnable usage
import { useState } from 'react';
import * as M from '@mantine/core';
import '@mantine/core/styles.css';
const tree = [{ value: "src", label: "src", children: [{ value: "components", label: "components" }, { value: "styles", label: "styles" }] }];
function ComponentPreview({ variant = 'Default' }: { variant?: string }) {
const [value, setValue] = useState(35);
return <M.Tree data={tree} />;
}
export default function Example({ variant = 'Default', theme = 'light' }: { variant?: string; theme?: 'light' | 'dark' }) {
return <M.MantineProvider forceColorScheme={theme} theme={{ respectReducedMotion: true }}><ComponentPreview variant={variant} /></M.MantineProvider>;
}
Preview states
Default
The example uses the native Mantine API. For supported input demos, change the local variant to Disabled, Invalid or Read only; these are example labels, not new component props. Keep the documented data, controlled values, stores and compound children when adapting the example.
Usage
The Tree component is used to display hierarchical data. The Tree component
has minimal styling by default; you can customize styles with Styles API.
Data prop
Data passed to the data prop should follow these rules:
- Data must be a stable reference (memoized)
- Data must be an array
- Each item in the array represents a node in the tree
- Each node must be an object with
valueandlabelkeys - Each node can have a
childrenkey with an array of child nodes - The
valueof each node must be unique
Valid data example:
// ✅ Valid data, all values are unique
const data = [
{
value: 'src',
label: 'src',
children: [
{ value: 'src/components', label: 'components' },
{ value: 'src/hooks', label: 'hooks' },
],
},
{ value: 'package.json', label: 'package.json' },
];
Invalid data example:
// ❌ Invalid data, values are not unique (components is used twice)
const data = [
{
value: 'src',
label: 'src',
children: [{ value: 'components', label: 'components' }],
},
{ value: 'components', label: 'components' },
];
Data type
You can import the TreeNodeData type to define the data type for your tree:
import { TreeNodeData } from '@mantine/core';
const data: TreeNodeData[] = [
{
value: 'src',
label: 'src',
children: [
{ value: 'src/components', label: 'components' },
{ value: 'src/hooks', label: 'hooks' },
],
},
{ value: 'package.json', label: 'package.json' },
];
renderNode
Use the renderNode prop to customize node rendering.
The renderNode function receives an object with the following properties as a single argument:
export interface RenderTreeNodePayload {
/** Node level in the tree */
level: number;
/** `true` if the node is expanded, applicable only for nodes with `children` */
expanded: boolean;
/** `true` if the node has non-empty `children` array or `hasChildren` is set to `true` */
hasChildren: boolean;
/** `true` if the node is selected */
selected: boolean;
/** `true` if the node's children are currently being loaded */
isLoading: boolean;
/** Error from the last failed load attempt, or `null` */
loadError: Error | null;
/** Node data from the `data` prop of `Tree` */
node: TreeNodeData;
/** Tree controller instance, return value of `useTree` hook */
tree: TreeController;
/** Props to spread into the root node element */
elementProps: {
className: string;
style: React.CSSProperties;
onClick: (event: React.MouseEvent) => void;
'data-selected': boolean | undefined;
'data-value': string;
};
/** Props to spread into the drag handle element when `withDragHandle` is set on `Tree`,
* `undefined` otherwise */
dragHandleProps: { onMouseDown: (event: React.MouseEvent) => void } | undefined;
}
useTree hook
useTree hook can be used to control selected and expanded state of the tree.
The hook accepts an object with the following properties:
export interface UseTreeInput {
/** Initial expanded state of all nodes, uncontrolled state */
initialExpandedState?: TreeExpandedState;
/** Expanded state of all nodes, controlled state */
expandedState?: TreeExpandedState;
/** Called when the tree expanded state changes */
onExpandedStateChange?: (expandedState: TreeExpandedState) => void;
/** Initial selected state of nodes */
initialSelectedState?: string[];
/** Selected state of all nodes, controlled state */
selectedState?: string[];
/** Called when the tree selected state changes */
onSelectedStateChange?: (selectedState: string[]) => void;
/** Initial checked state of nodes */
initialCheckedState?: string[];
/** Checked state of all nodes, controlled state */
checkedState?: string[];
/** Called when the tree checked state changes */
onCheckedStateChange?: (checkedState: string[]) => void;
/** Determines whether multiple node can be selected at a time */
multiple?: boolean;
/** Called with the node value when it is expanded */
onNodeExpand?: (value: string) => void;
/** Called with the node value when it is collapsed */
onNodeCollapse?: (value: string) => void;
/** Called when a node with `hasChildren: true` is expanded for the first time */
onLoadChildren?: (nodeValue: string) => Promise<void>;
/** When `true`, checking a parent does not affect children and vice versa.
* Each node's checked state is fully independent. @default false
*/
checkStrictly?: boolean;
}
And returns an object with the following properties:
export interface UseTreeReturnType {
/** When `true`, each node's checked state is independent (no parent-child cascading) */
checkStrictly: boolean;
/** Determines whether multiple node can be selected at a time */
multiple: boolean;
/** A record of `node.value` and boolean values that represent nodes expanded state */
expandedState: TreeExpandedState;
/** An array of selected nodes values */
selectedState: string[];
/** An array of checked nodes values */
checkedState: string[];
/** A value of the node that was last clicked
* Anchor node is used to determine range of selected nodes for multiple selection
*/
anchorNode: string | null;
/** Initializes tree state based on provided data, called automatically by the Tree component */
initialize: (data: TreeNodeData[]) => void;
/** Toggles expanded state of the node with provided value */
toggleExpanded: (value: string) => void;
/** Collapses node with provided value */
collapse: (value: string) => void;
/** Expands node with provided value */
expand: (value: string) => void;
/** Expands all nodes */
expandAllNodes: () => void;
/** Collapses all nodes */
collapseAllNodes: () => void;
/** Sets expanded state */
setExpandedState: React.Dispatch<
React.SetStateAction<TreeExpandedState>
>;
/** Toggles selected state of the node with provided value */
toggleSelected: (value: string) => void;
/** Selects node with provided value */
select: (value: string) => void;
/** Deselects node with provided value */
deselect: (value: string) => void;
/** Clears selected state */
clearSelected: () => void;
/** Sets selected state */
setSelectedState: React.Dispatch<React.SetStateAction<string[]>>;
/** Checks node with provided value */
checkNode: (value: string) => void;
/** Unchecks node with provided value */
uncheckNode: (value: string) => void;
/** Checks all nodes */
checkAllNodes: () => void;
/** Unchecks all nodes */
uncheckAllNodes: () => void;
/** Sets checked state */
setCheckedState: React.Dispatch<React.SetStateAction<string[]>>;
/** Returns all checked nodes with status */
getCheckedNodes: () => CheckedNodeStatus[];
/** Returns `true` if node with provided value is checked */
isNodeChecked: (value: string) => boolean;
/** Returns `true` if node with provided value is indeterminate */
isNodeIndeterminate: (value: string) => boolean;
/** Returns `true` if the node's children are currently being loaded */
isNodeLoading: (value: string) => boolean;
/** Returns the error from the last failed load attempt, or `null` */
getNodeLoadError: (value: string) => Error | null;
/** Programmatically triggers loading of a node's children */
loadNode: (value: string) => Promise<void>;
/** Clears the loaded cache for a node, causing it to re-fetch on next expand */
invalidateNode: (value: string) => void;
}
You can pass the value returned by the useTree hook to the tree prop of the Tree component
to control tree state:
Checked state
Tree can be used to display checked state with checkboxes.
To implement checked state, you need to render Checkbox.Indicator in the renderNode function:
To check/uncheck nodes, use checkAllNodes and uncheckAllNodes functions:
Check strictly
By default, checking a parent node also checks all of its children (and unchecking works
the same way). Set checkStrictly: true on useTree to make each node's checked state
fully independent – checking a parent does not affect children and vice versa.
In this mode, isNodeIndeterminate always returns false.
Initial expanded state
Expanded state is an object of node.value and boolean values that represent nodes expanded state.
To change initial expanded state, pass initialExpandedState to the useTree hook.
To generate expanded state from data with expanded nodes, you can use getTreeExpandedState function:
it accepts data and an array of expanded nodes values and returns expanded state object.
If '*' is passed as the second argument to getTreeExpandedState, all nodes will be expanded:
import { getTreeExpandedState } from '@mantine/core';
// Expand two given nodes
getTreeExpandedState(data, ['src', 'src/components']);
// Expand all nodes
getTreeExpandedState(data, '*');
Async loading
Tree supports lazy loading of children. Set hasChildren: true on a node without providing children –
when the node is expanded for the first time, onLoadChildren callback passed to useTree is called.
Use the mergeAsyncChildren utility to splice loaded children into your data:
import { mergeAsyncChildren, Tree, TreeNodeData, useTree } from '@mantine/core';
function Demo() {
const [data, setData] = useState<TreeNodeData[]>([
{ label: 'Documents', value: 'documents', hasChildren: true },
]);
const tree = useTree({
onLoadChildren: async (value) => {
const children = await fetchChildren(value);
setData((prev) => mergeAsyncChildren(prev, value, children));
},
});
return <Tree data={data} tree={tree} />;
}
The renderNode payload includes isLoading and loadError fields that you can use
to display a loading indicator or an error message. Use tree.invalidateNode(value) to clear
the cache for a node and allow re-fetching on next expand.
Search and filter
Tree does not include built-in search controls – search input and filtering logic are always external.
Use the filterTreeData utility to filter tree data based on a search query. The function
accepts tree data, a query string, and an optional custom filter function:
import { filterTreeData } from '@mantine/core';
// Filter with default case-insensitive label matching
const filtered = filterTreeData(data, 'button');
// Filter with a custom function
const filtered = filterTreeData(data, 'btn', (query, node) =>
node.value.includes(query)
);
The default filter compares the query against node.label (when it is a string) or node.value as a fallback.
Matching nodes and their ancestors are preserved in the result. You can provide a custom TreeNodeFilter
function for more advanced matching (for example, fuzzy search with fuse.js).
Highlight matching nodes
In this example, all nodes remain visible and matching text is highlighted using the Highlight component
inside renderNode. Ancestor nodes of matching nodes are auto-expanded.
Filter non-matching nodes
In this example, non-matching branches are removed from the tree using filterTreeData.
The filtered tree is auto-expanded with getTreeExpandedState(filteredData, '*').
Fuzzy search with fuse.js
You can pass a custom filter function to filterTreeData for fuzzy matching. This example
uses fuse.js:
Drag and drop
Tree component supports drag-and-drop reordering of nodes. To enable it, provide onDragDrop callback.
The callback receives an object with draggedNode (value of the dragged node), targetNode (value of the node it was dropped on),
and position ('before', 'after', or 'inside').
Use moveTreeNode utility function to update the data based on the drag-and-drop result:
import { moveTreeNode, Tree, TreeNodeData } from '@mantine/core';
function Demo() {
const [data, setData] = useState<TreeNodeData[]>(initialData);
return (
<Tree
data={data}
onDragDrop={(payload) =>
setData((current) => moveTreeNode(current, payload))
}
/>
);
}
When dragging over a node, the drop position is determined by cursor position:
- Top zone – drop before the target node (shown as a line above)
- Middle zone – drop inside the target node as a child (shown as a background highlight, only for nodes with children)
- Bottom zone – drop after the target node (shown as a line below)
Nodes cannot be dropped onto their own descendants.
Restricting drop targets
Use the allowDrop prop to forbid certain drops. The callback receives the same payload as
onDragDrop (draggedNode, targetNode, position) and should return false to reject the drop.
When it returns false, the drop indicator is hidden and the browser displays the
"not-allowed" cursor, so the user gets visual feedback before releasing the mouse.
Drag handle
By default, drag can be initiated from anywhere on a node. Set withDragHandle on Tree to
restrict drag initiation to an element that spreads dragHandleProps from the renderNode
payload. This is useful when a node contains interactive controls (inputs, buttons) that
would otherwise interfere with dragging.
Connecting lines
Set withLines prop to display connecting lines showing parent-child relationships.
Lines adapt to levelOffset spacing automatically.
Virtualization
Tree does not depend on any virtualization library – you supply one yourself.
Use the flattenTreeData utility to convert hierarchical data into a flat list of
visible nodes based on the current expanded state, then render each node with
FlatTreeNode which provides Tree's styles, aria attributes, click/keyboard
handlers, and renderNode support.
import { FlatTreeNode, flattenTreeData, useTree } from '@mantine/core';
const tree = useTree();
const flatList = flattenTreeData(data, tree.expandedState);
// flatList is FlattenedTreeNodeData[] – spread each entry into FlatTreeNode
FlatTreeNode accepts the same behavioral props as Tree (expandOnClick,
selectOnClick, expandOnSpace, checkOnSpace, renderNode) and a style prop
for virtualizer positioning. The container element must have data-tree-root
and role="tree" attributes for keyboard navigation to work.
Example: files tree
Example: docs navigation editor
The example below combines drag-and-drop, search with Highlight, single-selection,
hover-revealed actions menu, a folder page count badge and withLines into a single
documentation-navigation editor. The renderNode callback receives every payload field
the component exposes, so most application-level UX can be assembled from these primitives.
Source-backed parameter reference
Implementation signatures and types are generated from the pinned local source. Optional props, native inherited types, union branches and never exclusions are preserved. A forwardRef implementation's ref is a React reference, not an additional business prop.
Tree(_props)
export interface TreeNodeData {
label: React.ReactNode;
value: string;
nodeProps?: Record<string, any>;
children?: TreeNodeData[];
hasChildren?: boolean;
}
export interface RenderTreeNodePayload {
/** Node level in the tree */
level: number;
/** `true` if the node is expanded, applicable only for nodes with `children` */
expanded: boolean;
/** `true` if the node has non-empty `children` array or `hasChildren` is set to `true` in the data */
hasChildren: boolean;
/** `true` if the node is selected */
selected: boolean;
/** `true` if the node is at the top-most level of the tree (level 1) */
isRoot: boolean;
/** `true` if the node's children are currently being loaded */
isLoading: boolean;
/** Error from the last failed load attempt, or `null` */
loadError: Error | null;
/** Node data from the `data` prop of `Tree` */
node: TreeNodeData;
/** Tree controller instance, return value of `useTree` hook */
tree: TreeController;
/** Props to spread into the root node element */
elementProps: {
className: string;
style: React.CSSProperties;
onClick: (event: React.MouseEvent) => void;
'data-selected': boolean | undefined;
'data-value': string;
draggable?: boolean;
onDragStart?: (event: React.DragEvent) => void;
onDragOver?: (event: React.DragEvent) => void;
onDragLeave?: (event: React.DragEvent) => void;
onDrop?: (event: React.DragEvent) => void;
onDragEnd?: (event: React.DragEvent) => void;
};
/** Props to spread into the drag handle element when `withDragHandle` is set on `Tree`.
* `undefined` when `withDragHandle` is not enabled or drag-and-drop is disabled. */
dragHandleProps: TreeDragHandleProps | undefined;
}
export type RenderNode = (payload: RenderTreeNodePayload) => React.ReactNode;
export type TreeStylesNames = 'root' | 'node' | 'subtree' | 'label';
export type TreeCssVariables = {
root: '--level-offset';
};
export interface TreeProps extends BoxProps, StylesApiProps<TreeFactory>, ElementProps<'ul'> {
/** Data used to render nodes */
data: TreeNodeData[];
/** Horizontal padding of each subtree level, key of `theme.spacing` or any valid CSS value @default 'lg' */
levelOffset?: MantineSpacing;
/** If set, tree node with children is expanded on click @default true */
expandOnClick?: boolean;
/** If set, tree node with children is expanded on space key press @default true */
expandOnSpace?: boolean;
/** If set, tree node is checked on space key press @default false */
checkOnSpace?: boolean;
/** If set, tree node is selected on click @default false */
selectOnClick?: boolean;
/** Use-tree hook instance that can be used to manipulate component state */
tree?: TreeController;
/** A function to render tree node label */
renderNode?: RenderNode;
/** If set, selection is cleared when user clicks outside of the tree @default false */
clearSelectionOnOutsideClick?: boolean;
/** If set, tree nodes range can be selected with click when `Shift` key is pressed @default true */
allowRangeSelection?: boolean;
/** If set, subtree content is kept mounted when collapsed. React 19 `Activity` is used to preserve state. @default false */
keepMounted?: boolean;
/** Called when a node is dropped on another node, enables drag-and-drop when provided */
onDragDrop?: (payload: TreeDragDropPayload) => void;
/** Called for each potential drop target to determine whether a drop is allowed.
* When it returns `false`, the drop indicator is hidden and the drop is rejected. */
allowDrop?: TreeAllowDrop;
/** If set, drag-and-drop must be initiated from an element that spreads `dragHandleProps`
* from the `renderNode` payload, rather than anywhere on the node. @default false */
withDragHandle?: boolean;
/** If set, connecting lines are rendered showing parent-child relationships @default false */
withLines?: boolean;
}
export type TreeFactory = Factory<{
props: TreeProps;
ref: HTMLUListElement;
stylesNames: TreeStylesNames;
vars: TreeCssVariables;
}>;
export type Mod = Record<string, any> | string;
export type BoxMod = Mod | Mod[] | BoxMod[];
export interface BoxProps extends MantineStyleProps {
/** Class added to the root element, if applicable */
className?: string | undefined;
/** Inline style added to root component element, can subscribe to theme defined on MantineProvider */
style?: MantineStyleProp;
/** CSS variables defined on root component element */
__vars?: CssVarsProp;
/** `size` property passed down the HTML element */
__size?: string;
/** Breakpoint above which the component is hidden with `display: none` */
hiddenFrom?: MantineBreakpoint;
/** Breakpoint below which the component is hidden with `display: none` */
visibleFrom?: MantineBreakpoint;
/** Determines whether component should be hidden in light color scheme with `display: none` */
lightHidden?: boolean;
/** Determines whether component should be hidden in dark color scheme with `display: none` */
darkHidden?: boolean;
/** Element modifiers transformed into `data-` attributes, for example, `{ 'data-size': 'xl' }`, falsy values are removed */
mod?: BoxMod;
}
export type ElementProps<
ElementType extends React.ElementType,
PropsToOmit extends string = never,
> = Omit<React.ComponentProps<ElementType>, 'style' | PropsToOmit>;
export interface CSSProperties extends React.CSSProperties {
[key: string]: any;
}
type MantineStyle = CSSProperties | ((theme: MantineTheme) => CSSProperties);
export type MantineStyleProp = MantineStyle | MantineStyle[] | MantineStyleProp[] | undefined;
export type CssVariable = `--${string}`;
export type CssVariables<Variable extends string = CssVariable> = Partial<Record<Variable, string>>;
export type CssVars<Variable extends string = CssVariable> =
| CssVariables<Variable>
| ((theme: MantineTheme) => CssVariables<Variable>)
| CssVars<Variable>[];
export type CssVarsProp<Variable extends string = CssVariable> =
| CssVars<Variable>
| CssVars<Variable>[];
Native and external types
External types remain defined by React or native dependencies, not manually expanded into guessed props. The installed version's TypeScript definitions are authoritative.
Factory<{
props: TreeProps;
ref: HTMLUListElement;
stylesNames: TreeStylesNames;
vars: TreeCssVariables;
}>
MantineStyleProps
React.CSSProperties
MantineTheme
Partial<Record<Variable, string>>
Record<Variable, string>
Variable
MantineBreakpoint
Record<string, any>
StylesApiProps<TreeFactory>
React.ElementType
Omit<React.ComponentProps<ElementType>, 'style' | PropsToOmit>
React.ComponentProps<ElementType>
ElementType
PropsToOmit
React.ReactNode
MantineSpacing
TreeController
Error
React.MouseEvent
React.DragEvent
TreeDragHandleProps
TreeDragDropPayload
TreeAllowDrop
HTMLUListElement