Skip to content

TreeMenu

import TreeMenu from 'react-simple-tree-menu';
PropTypeDefaultDescription
dataTreeNodeObject | TreeNodeInArray[]requiredThe tree to render. See Data formats.
onClickItem(item: Item) => void() => {}Fires on item click with the full Item (including any custom user props you put on nodes).
initialActiveKeystringundefinedInitial active/selected key. Read once.
initialFocusKeystringundefinedInitial focused key for keyboard. Read once.
initialOpenNodesstring[][]Keys of nodes that start open. Read once.
activeKeystringControlled active key.
focusKeystringControlled focus key.
openNodesstring[]Controlled open-nodes list.
hasSearchbooleantrueWhether to pass a search callback to the render-props function.
debounceTimenumber125Milliseconds between last keystroke and filter.
localeLocaleFunctionTransform labels before matching/render. See Types.
matchSearchMatchSearchFunctionReplace the default substring matcher. See Types.
disableKeyboardbooleanfalseSkip the <div> keyboard wrapper.
resetOpenNodesOnDataUpdatebooleanfalseReset open nodes when data identity changes.
classNamesTreeMenuClassNames{}Class names appended to rstm-* anchors. See Theming.
labelsTreeMenuLabels{}Customize the default UI’s i18n strings.
keySeparatorstring'/'Delimiter joining node keys into paths. Change if your keys contain / (URLs, paths) — the library uses it everywhere: emitted Item.key, openNodes lookups, LEFT-arrow parent focus, unflatten().
children(props: TreeMenuChildren) => ReactNodedefaultChildrenRender-props function.

Attach a ref to reset state programmatically:

import TreeMenu, { type TreeMenuHandle } from 'react-simple-tree-menu';
import { useRef } from 'react';
function MyTree() {
const ref = useRef<TreeMenuHandle>(null);
return (
<>
<button onClick={() => ref.current?.resetOpenNodes()}>Reset</button>
<button onClick={() => ref.current?.expandAll()}>Expand all</button>
<button onClick={() => ref.current?.collapseAll()}>Collapse all</button>
<TreeMenu ref={ref} data={data} />
</>
);
}
MethodSignatureNotes
resetOpenNodes(openNodes?, activeKey?, focusKey?) => voidReset expansion state. Also clears activeKey/focusKey/searchTerm.
expandAll() => voidOpen every branch. Preserves active/focus/search. No-op under controlled openNodes.
collapseAll() => voidClose every branch. Preserves active/focus/search. No-op under controlled openNodes.

Controlled openNodes users: the imperative methods are no-ops (the parent owns the slot). Use the exported helper instead:

import TreeMenu, { collectBranchKeys } from 'react-simple-tree-menu';
const [open, setOpen] = useState<string[]>([]);
<button onClick={() => setOpen(collectBranchKeys(data))}>Expand all</button>
<TreeMenu data={data} openNodes={open} />

collectBranchKeys(data, keySeparator?) is O(N) and microseconds even on 100k-node trees. The real cost after expand-all is rendering every branch’s children — for trees that grow beyond ~2k visible rows, pair with the virtualization guide.

When you pass a function as children, it receives:

type TreeMenuChildren = {
items: Item[]; // Flattened, filtered, search-aware
search?: (term: string) => void; // Call to filter (undefined if hasSearch=false)
searchTerm?: string; // Current search term
resetOpenNodes: (
openNodes?: string[],
activeKey?: string,
focusKey?: string
) => void;
};

Each entry in items is a TreeMenuItem — an Item (what walk() emits) decorated with per-render handlers and state flags:

import type { MouseEvent } from 'react';
interface Item {
key: string; // Unique key path, e.g. "fruit/apple"
label: string; // Display label (after `locale` if provided)
hasNodes: boolean; // True if the item has children
isOpen: boolean; // True if the item is expanded
level: number; // 0-indexed depth
posInSet?: number; // 1-based position among siblings (WAI-ARIA)
setSize?: number; // Number of siblings (WAI-ARIA)
[name: string]: unknown; // Any custom fields from the original node
}
interface TreeMenuItem extends Item {
active?: boolean; // Matches `activeKey`
focused?: boolean; // Matches `focusKey`
onClick: (event: MouseEvent<HTMLLIElement>) => void;
toggleNode?: () => void; // Toggles open state (branches only)
}

The default UI fires onClick from a <div>, not a <li> — the type describes what <ItemComponent> was historically placed on. If you pass the handler to a different element in a custom render-prop, you may need an event-type cast.

Helper for custom render-props that reconstructs a nested tree shape from the flat items array walk() emits. Group-by-parent, zero React.

import { unflatten } from 'react-simple-tree-menu';
function unflatten<T extends { key: string }>(
items: readonly T[]
): { roots: T[]; childrenByParent: Map<string, T[]> };
  • Generic over T extends { key: string } — pass raw Item, TreeMenuItem, or any consumer-projected shape.
  • childrenByParent is a Map from each parent’s key to its ordered children (input order preserved).
  • Same helper defaultChildren uses internally, so your custom render-props share the same grouping contract as the default UI.

See the render-props guide for a complete nested-DOM example.