Tree
A hierarchical tree view for file explorers and nested navigation.
Data-driven and controlled: pass a nested data array and the
Tree renders recursively. A node with children is a folder;
a leaf fires onSelect. Full keyboard support
(↑/↓ move, →/←
expand/collapse, Enter select).
File explorer
Click a folder to expand/collapse, a file to select. State is fully
controlled — selectedId/onSelect and
expandedIds (here seeded with
defaultExpandedIds). README.md is disabled.
- src
- index.ts
- components
- Button.tsx
- Tree.tsx
- utils.ts
- package.json
- README.md
Selected: src/components/Tree.tsx
import { useState } from "react";
import { Tree } from "@usevyre/react";
const data = [
{ id: "src", label: "src", children: [
{ id: "src/index.ts", label: "index.ts" },
{ id: "src/components", label: "components", children: [
{ id: "src/components/Button.tsx", label: "Button.tsx" },
{ id: "src/components/Tree.tsx", label: "Tree.tsx" },
]},
{ id: "src/utils.ts", label: "utils.ts" },
]},
{ id: "package.json", label: "package.json" },
{ id: "README.md", label: "README.md", disabled: true },
];
const [selected, setSelected] = useState<string | null>(
"src/components/Tree.tsx"
);
<Tree
data={data}
selectedId={selected}
onSelect={setSelected}
defaultExpandedIds={["src", "src/components"]}
/> <script setup>
import { ref } from "vue";
import { Tree } from "@usevyre/vue";
const data = [
{ id: "src", label: "src", children: [
{ id: "src/index.ts", label: "index.ts" },
{ id: "src/components", label: "components", children: [
{ id: "src/components/Button.tsx", label: "Button.tsx" },
{ id: "src/components/Tree.tsx", label: "Tree.tsx" },
]},
{ id: "src/utils.ts", label: "utils.ts" },
]},
{ id: "package.json", label: "package.json" },
{ id: "README.md", label: "README.md", disabled: true },
];
const selected = ref("src/components/Tree.tsx");
</script>
<template>
<Tree
:data="data"
v-model:selected="selected"
:default-expanded-ids="['src', 'src/components']"
/>
</template> Props
Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | TreeNode[] | — | Nested { id, label, icon?, disabled?, children? }. |
expandedIds | string[] | — | Controlled expanded node ids. Omit for uncontrolled. |
defaultExpandedIds | string[] | [] | Initially expanded ids when uncontrolled. |
onExpandedChange | (ids: string[]) => void | — | Fires when a folder is expanded/collapsed. |
selectedId | string | null | — | Controlled selected node id. |
defaultSelectedId | string | null | null | Initial selection when uncontrolled. |
onSelect | (id: string) => void | — | Fires with the node id (not an event). |
Data-driven, not composed. Tree takes a nested array, not
nested children — so AI cannot mis-nest markup, and expansion/selection
stay controlled state instead of leaking into the DOM.
Common AI mistakes
- Rendering a nested <ul> tree by hand with manual expand state→ Pass a nested `data` array to <Tree> and control expandedIds/selectedId
- onSelect={(e) => ...}→ onSelect={(id) => setSelected(id)}
- Mutating the data array to expand/collapse→ Track expandedIds in state (or use defaultExpandedIds)
- Using DropdownMenu submenus for a file tree→ Use <Tree> for file explorers / nested nav
Quick examples
File explorer, controlled selection
const [sel, setSel] = useState<string | null>("src/a.ts");
<Tree
data={[
{ id: "src", label: "src", children: [
{ id: "src/a.ts", label: "a.ts" },
{ id: "src/b", label: "b", children: [
{ id: "src/b/c.ts", label: "c.ts" },
]},
]},
{ id: "README.md", label: "README.md" },
]}
selectedId={sel}
onSelect={setSel}
defaultExpandedIds={["src"]}
/>Fully controlled expansion
const [open, setOpen] = useState<string[]>(["root"]);
<Tree data={tree} expandedIds={open} onExpandedChange={setOpen} />