Case files
<script setup lang="ts">
import { TreeView, createTreeCollection } from "@dicehub/kappa/components/tree-view";
const caseNode = {
value: "case",
label: "drivaerTest",
children: [
{ value: "constant", label: "constant" },
{ value: "system", label: "system" },
],
};
const collection = createTreeCollection({
rootNode: { value: "ROOT", label: "", children: [caseNode] },
});
</script>
<template>
<TreeView.Root :collection="collection" :default-expanded-value="['case']">
<TreeView.Label>Case files</TreeView.Label>
<TreeView.Tree>
<TreeView.NodeProvider :node="caseNode" :index-path="[0]">
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchTrigger>
<TreeView.BranchIndicator />
</TreeView.BranchTrigger>
<TreeView.BranchText>{{ caseNode.label }}</TreeView.BranchText>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.NodeProvider
v-for="(node, index) in caseNode.children"
:key="node.value"
:node="node"
:index-path="[0, index]"
>
<TreeView.Item><TreeView.ItemText>{{ node.label }}</TreeView.ItemText></TreeView.Item>
</TreeView.NodeProvider>
</TreeView.BranchContent>
</TreeView.Branch>
</TreeView.NodeProvider>
</TreeView.Tree>
</TreeView.Root>
</template>Choose a Tree
| Component | Use | Engine |
|---|---|---|
TreeView | Small and medium trees with full compound composition. | Ark UI Tree View |
VirtualTree | Large and dense trees, including 100,000 or more nodes. | Kappa indexed fixed-row engine |
Choose the component explicitly. Kappa does not change rendering engines after the tree mounts.
Installation
Both components are part of the main Kappa package. Granular imports keep the large-tree engine separate.
Barrel
import {
TreeView,
VirtualTree,
createTreeCollection,
} from "@dicehub/kappa";Granular
import {
TreeView,
createTreeCollection,
} from "@dicehub/kappa/components/tree-view";
import { VirtualTree } from "@dicehub/kappa/components/virtual-tree";Standard Tree
TreeView.Root
├── TreeView.Label
└── TreeView.Tree
└── TreeView.NodeProvider (recursive)
├── TreeView.Branch
│ ├── TreeView.BranchControl
│ │ ├── TreeView.BranchTrigger
│ │ │ └── TreeView.BranchIndicator
│ │ └── TreeView.BranchText
│ └── TreeView.BranchContent
│ └── child NodeProvider parts
└── TreeView.Item
└── TreeView.ItemTextTreeView keeps Ark UI controlled state, lazy loading, selection, checking, typeahead, rename, focus, and keyboard behavior.
Render one NodeProvider for each node. Its indexPath must match the collection position.
Large Data
VirtualTree indexes all source nodes once. It renders only the viewport and overscan rows.
<script setup lang="ts">
import { VirtualTree } from "@dicehub/kappa/components/virtual-tree";
const regions = Array.from({ length: 100 }, (_, region) => ({
value: `region-${region}`,
label: `Region ${region + 1}`,
children: Array.from({ length: 1_000 }, (_, cell) => ({
value: `region-${region}-cell-${cell}`,
label: `Cell ${region * 1_000 + cell + 1}`,
})),
}));
</script>
<template>
<VirtualTree
:items="regions"
aria-label="Mesh regions"
:default-expanded-value="['region-0']"
height="22rem"
/>
</template>Examples
Multiple Selection
Use Ark UI selection behavior for a small or medium tree.
Case files
<TreeView.Root
v-model:selected-value="selectedValues"
:collection="collection"
selection-mode="multiple"
>
<!-- Render branches and items with stable values and index paths. -->
</TreeView.Root>Async Loading
Load each branch independently. A loading request does not lock the rest of the tree.
<script setup lang="ts">
import { VirtualTree } from "@dicehub/kappa/components/virtual-tree";
const items = [{
value: "results",
label: "Result snapshots",
childrenCount: 3,
}];
const loadChildren = async ({ value }: { value: string }) => {
const response = await fetch(`/api/tree/${value}`);
return response.json();
};
</script>
<template>
<VirtualTree
:items="items"
:load-children="loadChildren"
aria-label="Result snapshots"
/>
</template>Custom Data
Map existing data without copying every source node. Row actions stay separate from tree selection.
<script setup lang="ts">
import { VirtualTree } from "@dicehub/kappa/components/virtual-tree";
const entries = [{
key: "boundaries",
name: "Boundary groups",
entries: [
{ key: "inlet", name: "inlet" },
{ key: "outlet", name: "outlet" },
],
}];
</script>
<template>
<VirtualTree
:items="entries"
:node-to-value="(node) => node.key"
:node-to-string="(node) => node.name"
:node-to-children="(node) => node.entries"
aria-label="Mesh boundaries"
>
<template #default="{ node }">
<span>{{ node.name }}</span>
<button data-kappa-tree-interactive>Inspect</button>
</template>
</VirtualTree>
</template>Performance Contract
VirtualTreerequires stable, unique string values and a fixed row height.- Node lookup is indexed. Adjacent keyboard movement uses visible indexes.
- Expand and collapse update the visible range. Ordinary scrolling never flattens the source tree.
- Replace
itemsonly when source structure changes. Keep large node objects outside deep Vue reactivity.
Run pnpm --filter @dicehub/kappa benchmark:virtual-tree to measure both 100,000-node shapes on the current machine.
Correspondence
| Reference | Corresponding component | Role in Kappa |
|---|---|---|
| Kumo | None | No corresponding component. |
| Ark UI | Tree View | Behavior and accessibility base for TreeView. |
| shadcn | None | No official Tree View component. |
Accessibility
- Both components use
treeandtreeitemsemantics. VirtualTreeaddsaria-level,aria-posinset, andaria-setsizebecause most rows are not mounted.- Arrow keys, Home, End, Page Up, Page Down, selection, and typeahead work from the tree focus target.
- Use
data-kappa-tree-interactiveon a custom row action. Its click will not select or expand the row. - Selection and focus are separate states. Keep both states visible.
API Reference
TreeView.Root
| Prop | Type | Default | Description |
|---|---|---|---|
collection | TreeCollection<T> | — | Indexed Ark UI tree collection. |
expandedValue | string[] | — | Controlled expanded branch values. |
selectedValue | string[] | — | Controlled selected node values. |
selectionMode | "single" | "multiple" | "single" | Selection behavior. |
loadChildren | (details) => Promise<T[]> | — | Loads branch children before expansion. |
typeahead | boolean | true | Enables label typeahead. |
VirtualTree
| Prop | Type | Default | Description |
|---|---|---|---|
ariaLabel | string | "Tree" | Accessible tree name. |
defaultExpandedValue | string[] | [] | Initial uncontrolled expanded values. |
defaultFocusedValue | string | — | Initial uncontrolled focused value. |
defaultSelectedValue | string[] | [] | Initial uncontrolled selected values. |
disabled | boolean | false | Disables tree interaction. |
expandOnClick | boolean | true | Expands or collapses a branch when its row is clicked. |
expandedValue | string[] | — | Controlled expanded branch values. |
focusedValue | string | null | — | Controlled focused value. |
height | CSS height | "20rem" | Scroll viewport height. |
id | string | generated | Stable root and row ID prefix. |
indent | number | 16 | Indent for each tree level in pixels. |
isNodeDisabled | (node) => boolean | node.disabled | Returns the disabled state. |
items | readonly T[] | — | Source tree nodes. |
loadChildren | (details) => Promise<T[]> | — | Loads one unloaded branch. |
nodeToChildren | (node) => T[] | node.children | Returns loaded child nodes. |
nodeToChildrenCount | (node) => number | node.childrenCount | Reports unloaded children. |
nodeToString | (node) => string | node.label | Returns the visible and typeahead label. |
nodeToValue | (node) => string | node.value | Returns the stable unique node value. |
overscan | number | 8 | Extra rows rendered around the viewport. |
rowHeight | number | 28 | Fixed row height in pixels. |
selectedValue | string[] | — | Controlled selected node values. |
selectionMode | "single" | "multiple" | "single" | Single or range-capable multiple selection. |
typeahead | boolean | true | Moves focus by typed label prefix. |
VirtualTree Methods
| Method | Description |
|---|---|
scrollToValue(value, align?) | Expands loaded ancestors and scrolls the node. Controlled expansion must be applied by the parent. |
focus(value) | Scrolls to and focuses a node. |
expand(value, recursive?) | Expands one branch or all loaded descendants. |
collapse(value, recursive?) | Collapses one branch or all loaded descendants. |
getNode(value) | Returns the source node from the constant-time index. |
Shared Events
| Event | Description |
|---|---|
expandedChange | Runs after a branch expansion request. |
selectionChange | Runs after node selection changes. |
focusChange | Runs after the focused value changes. |
loadChildrenComplete | Runs after lazy children enter the index. |
loadChildrenError | Runs when lazy loading fails. |