TreeView
The TkTreeview component displays hierarchical data in a tree structure with
expandable/collapsible nodes. Uses array-based data structure for better
performance and easier data management.
- React
- Vue
- Angular
import { TkTreeView } from '@takeoff-ui/react'
import { TkTreeView } from '@takeoff-ui/vue'
import { TkTreeView } from '@takeoff-ui/angular'
Playground
Preview
Generated Code
<TkTreeView items={[{"key":"1","label":"Documents","children":[{"key":"1-1","label":"Report.pdf"},{"key":"1-2","label":"Summary.docx"}]},{"key":"2","label":"Images","children":[{"key":"2-1","label":"Photo.png"},{"key":"2-2","label":"Logo.svg"}]}]} mode="basic" type="basic" size="base" selectionStrategy="all" toggleTrigger="item" branchIcon="category" leafIcon="label" showBadge showPointer/><TkTreeView :items="[{"key":"1","label":"Documents","children":[{"key":"1-1","label":"Report.pdf"},{"key":"1-2","label":"Summary.docx"}]},{"key":"2","label":"Images","children":[{"key":"2-1","label":"Photo.png"},{"key":"2-2","label":"Logo.svg"}]}]" mode="basic" type="basic" size="base" selectionStrategy="all" toggleTrigger="item" branchIcon="category" leafIcon="label" :showBadge.prop="true" :showPointer.prop="true"/><tk-tree-view [items]="[{"key":"1","label":"Documents","children":[{"key":"1-1","label":"Report.pdf"},{"key":"1-2","label":"Summary.docx"}]},{"key":"2","label":"Images","children":[{"key":"2-1","label":"Photo.png"},{"key":"2-2","label":"Logo.svg"}]}]" mode="basic" type="basic" size="base" selectionStrategy="all" toggleTrigger="item" branchIcon="category" leafIcon="label" [showBadge]="true" [showPointer]="true"/>Controls
Type
The type prop controls the style type of the tree view. It can be set to
basic, divided, or light.
- React
- Vue
- Angular
Mode
The mode prop controls the display mode of the tree view. It can be set to
basic or stepper.
Basic Mode
Displays tree items in a traditional hierarchical structure with expandable/collapsible nodes.
Stepper Mode
Displays tree items in a step-by-step column layout, ideal for navigation and drill-down interfaces.
- React
- Vue
- Angular
Size
The size prop controls the size of the tree view. It can be set to base,
small, or large.
- React
- Vue
- Angular
Selectable
Enable checkbox selection with the selectable prop for multi-selection
functionality. Use the value prop to control selected items and listen to
tk-change events for selection updates.
The selectAll prop displays a "Select All" checkbox above the tree with a
badge counter showing the total number of selected items. The selection behavior
respects the selectionStrategy prop:
all: Selects both branches and leaf nodesleaf: Selects only leaf nodes (file nodes)
Selected items: 0
- React
- Vue
- Angular
const [selectedItems, setSelectedItems] = useState([]);
const treeData = [
{
key: 'documents',
label: 'Documents',
children: [
{
key: 'work-files',
label: 'Work Files',
children: [
{
key: 'reports',
label: 'Reports',
children: [
{
key: 'q1-report',
label: 'Q1 Report.pdf'
},
{
key: 'q2-report',
label: 'Q2 Report.pdf'
}
]
}
]
},
{
key: 'personal',
label: 'Personal',
children: [
{
key: 'photos',
label: 'Photos',
children: [
{
key: 'vacation-jpg',
label: 'Vacation.jpg'
}
]
},
{
key: 'notes-txt',
label: 'Notes.txt'
}
]
}
]
}
];
<TkTreeView
type="light"
size="base"
items={treeData}
selectable={true}
selectAll={true}
selectAllLabel="Select All"
selectionStrategy="all"
value={selectedItems}
branchIcon="folder"
leafIcon="insert_drive_file"
onTkChange={(e) => setSelectedItems(e.detail)}
/>
<div>
<p>Selected items: {selectedItems.length}</p>
{selectedItems.length > 0 && (
<ul>
{selectedItems.map((key, index) => (
<li key={index}>{key}</li>
))}
</ul>
)}
</div>
<script setup>
import { ref } from 'vue';
const selectedItems = ref([]);
const treeData = [
{
key: 'documents',
label: 'Documents',
children: [
{
key: 'work-files',
label: 'Work Files',
children: [
{
key: 'reports',
label: 'Reports',
children: [
{
key: 'q1-report',
label: 'Q1 Report.pdf'
},
{
key: 'q2-report',
label: 'Q2 Report.pdf'
}
]
}
]
},
{
key: 'personal',
label: 'Personal',
children: [
{
key: 'photos',
label: 'Photos',
children: [
{
key: 'vacation-jpg',
label: 'Vacation.jpg'
}
]
},
{
key: 'notes-txt',
label: 'Notes.txt'
}
]
}
]
}
];
</script>
<template>
<TkTreeView
type="light"
size="base"
:items="treeData"
:selectable="true"
:selectAll="true"
selectAllLabel="Select All"
:selectionStrategy="selectionStrategy"
v-model="selectedItems"
branchIcon="folder"
leafIcon="insert_drive_file"
@tk-change="(e) => selectedItems = e.detail"
/>
<div>
<p>Selected items: {{ selectedItems.length }}</p>
<ul v-if="selectedItems.length > 0">
<li v-for="(key, index) in selectedItems" :key="index">{{ key }}</li>
</ul>
</div>
</template>
Expand All
The expandAll prop controls the initial expansion state of tree nodes.
Basic Mode with expandAll
Expands all directory nodes throughout the tree, showing the complete hierarchical structure.
Stepper Mode with expandAll
Expands the first directory path all the way down to show the deepest level in a column layout.
- React
- Vue
- Angular
Expanded Keys
The expandedKeys prop to control the expanded items.
- React
- Vue
- Angular
Collapse Behavior
By default clicking anywhere on a branch item expands or collapses it, so a branch closes when it is clicked while one of its children is highlighted.
Set toggleTrigger="icon" to limit toggling to the arrow icon. Clicking the
rest of the item then only highlights it and leaves the tree open.
The highlight always follows the last clicked item, so expanding and collapsing a branch both highlight that branch and the highlight never disappears while the user is only opening and closing branches.
Clicking the highlighted item again removes the highlight. A branch click with
the item trigger expands or collapses instead, so it keeps the highlight
rather than blinking it on and off on alternating clicks.
Only the arrow icon toggles the branch, clicking the rest of the item just selects it.
- React
- Vue
- Angular
Lazy Loading
Set the lazy prop to fetch a branch's children only when it is expanded.
Expanding a branch that has none emits tk-load with the item and its index
path, and the fetch is yours to make — the same split as tk-request on the
table.
Branches that are not loaded yet have to be marked hasChildren: true,
otherwise they cannot be told apart from a leaf and cannot be expanded at all.
Put the children you fetch onto that branch in items, and list the keys in
flight in loadingKeys to give them a spinner. Clear that list in a finally,
or a failed fetch leaves one spinning forever.
- React
- Vue
- Angular
const initialData = [
{ key: 'documents', label: 'Documents', hasChildren: true },
{ key: 'images', label: 'Images', hasChildren: true },
{ key: 'readme', label: 'readme.txt' },
];
// Rebuilds the branch the children belong to, leaving every other node as it was.
const attachChildren = (nodes, key, children) =>
nodes.map((node) => {
if (node.key === key) {
return { ...node, children, hasChildren: children.length > 0 };
}
if (node.children) {
return { ...node, children: attachChildren(node.children, key, children) };
}
return node;
});
const LazyTree = () => {
const [items, setItems] = useState(initialData);
const [loadingKeys, setLoadingKeys] = useState([]);
const [loadedKeys, setLoadedKeys] = useState([]);
const inFlight = useRef(new Set());
const handleLoad = async (event) => {
const { key } = event.detail.item;
// Collapsing and reopening a branch faster than loadingKeys can round-trip
// through state emits tk-load twice, so keys in flight are tracked in a ref.
if (inFlight.current.has(key)) return;
inFlight.current.add(key);
setLoadingKeys((keys) => [...keys, key]);
try {
const children = await fetch(`/api/folders/${key}`).then((res) => res.json());
setItems((current) => attachChildren(current, key, children));
// Only a branch that actually arrived is marked as loaded; a failed one is
// left off the list so expanding it again retries the fetch.
setLoadedKeys((keys) => [...keys, key]);
} finally {
inFlight.current.delete(key);
setLoadingKeys((keys) => keys.filter((k) => k !== key));
}
};
return (
<TkTreeView
lazy
type="light"
items={items}
loadingKeys={loadingKeys}
loadedKeys={loadedKeys}
branchIcon="folder"
leafIcon="insert_drive_file"
onTkLoad={handleLoad}
/>
);
};
<script setup>
import { ref } from 'vue';
const items = ref([
{ key: 'documents', label: 'Documents', hasChildren: true },
{ key: 'images', label: 'Images', hasChildren: true },
{ key: 'readme', label: 'readme.txt' },
]);
const loadingKeys = ref([]);
const loadedKeys = ref([]);
// Rebuilds the branch the children belong to, leaving every other node as it was.
const attachChildren = (nodes, key, children) =>
nodes.map((node) => {
if (node.key === key) {
return { ...node, children, hasChildren: children.length > 0 };
}
if (node.children) {
return { ...node, children: attachChildren(node.children, key, children) };
}
return node;
});
const handleLoad = async (event) => {
const { key } = event.detail.item;
loadingKeys.value = [...loadingKeys.value, key];
try {
const children = await fetch(`/api/folders/${key}`).then((res) => res.json());
items.value = attachChildren(items.value, key, children);
loadedKeys.value = [...loadedKeys.value, key];
} finally {
loadingKeys.value = loadingKeys.value.filter((k) => k !== key);
}
};
</script>
<template>
<TkTreeView
:lazy="true"
type="light"
:items="items"
:loadingKeys="loadingKeys"
:loadedKeys="loadedKeys"
branchIcon="folder"
leafIcon="insert_drive_file"
@tk-load="handleLoad"
/>
</template>
import { Component } from '@angular/core';
@Component({
selector: 'app-lazy-tree',
template: `
<tk-tree-view
[lazy]="true"
type="light"
[items]="items"
[loadingKeys]="loadingKeys"
[loadedKeys]="loadedKeys"
branchIcon="folder"
leafIcon="insert_drive_file"
(tk-load)="handleLoad($event)"
></tk-tree-view>
`,
})
export class LazyTreeComponent {
items = [
{ key: 'documents', label: 'Documents', hasChildren: true },
{ key: 'images', label: 'Images', hasChildren: true },
{ key: 'readme', label: 'readme.txt' },
];
loadingKeys: string[] = [];
loadedKeys: string[] = [];
async handleLoad(event) {
const key = event.detail.item.key;
this.loadingKeys = [...this.loadingKeys, key];
try {
const children = await fetch(`/api/folders/${key}`).then((res) => res.json());
this.items = this.attachChildren(this.items, key, children);
this.loadedKeys = [...this.loadedKeys, key];
} finally {
this.loadingKeys = this.loadingKeys.filter((k) => k !== key);
}
}
private attachChildren(nodes, key, children) {
return nodes.map((node) => {
if (node.key === key) {
return { ...node, children, hasChildren: children.length > 0 };
}
if (node.children) {
return { ...node, children: this.attachChildren(node.children, key, children) };
}
return node;
});
}
}
Reopening a branch asks again, which is how a failed fetch is retried.
expandAll is ignored here, so expandedKeys is the way to expand a lazy
branch programmatically, and a branch it opens is requested just like a clicked
one. Selection only ever covers the branches that are loaded.
Custom Styles
The containerStyle and stepStyle props allow you to apply custom styles. The
stepStyle prop only works in stepper mode.
In this demo, background color is red for the container and vertical scrolling is applied to the step using max height.
- React
- Vue
- Angular
<TkTreeView
containerStyle={{
backgroundColor: '#f8d7da',
padding: '10px',
borderRadius: '8px',
}}
stepStyle={{ backgroundColor: 'rgb(241 241 241)', maxHeight: '200px', overflowY: 'auto', minWidth: '200px' }}
mode="stepper"
type="divided"
size="base"
items={sampleData}
branchIcon="folder"
/>
<tk-tree-view
:container-style="{
backgroundColor: '#f8d7da',
padding: '10px',
borderRadius: '8px',
}"
:step-style="{
backgroundColor: 'rgb(241 241 241)',
maxHeight: '200px',
overflowY: 'auto',
minWidth: '200px'
}"
mode="stepper"
type="divided"
size="base"
:items="sampleData"
branchIcon="folder"
></tk-tree-view>
TreeView API
Props
| Name | Type | Default | Description |
|---|---|---|---|
IBadgeOptions | null | Badge customization options for children count display. | |
string | '' | Icon for branch items (items with children). When empty, no icon is shown. | |
CSSStyleProperties | null | The style attribute of container element | |
boolean | false | If true, disables all interaction with the tree view. | |
boolean | false | If true, expands all nodes in basic mode. Note: This prop is ignored when expandedKeys is provided. | |
string[] | null | Array of keys that should be expanded. Usage: Provide an array of item keys: ["atakan", "mehmet", "4"] Each key must be unique in the tree structure | |
ITreeItem[] | [] | Array of tree items data. This is the primary way to provide data to the tree view. | |
boolean | false | If true, branches are loaded on demand rather than handed over up front. Expanding a branch whose children are missing emits tk-load instead of rendering nothing, and the fetch itself belongs to the consumer. Note: Branches that are not loaded yet have to be marked with hasChildren: true, otherwise they cannot be told apart from a leaf and cannot be expanded at all. Items also need a key, since loadingKeys and loadedKeys address them by key. expandAll is ignored while this is set, because the tree it would expand is not loaded yet. | |
string | '' | Icon for leaf items (items without children). When empty, no icon is shown. | |
string[] | [] | Keys of the branches whose children have already been fetched. A branch listed here never emits tk-load again, and leaving a failed branch off the list is what allows it to be retried. Note: This is optional. A branch that came back with children is already recognised as loaded, so the only case that needs this list is a branch that came back empty and should still read as a branch. Marking such a branch hasChildren: false instead turns it into a leaf and takes its arrow away. | |
string[] | [] | Keys of the branches whose children are being fetched right now. Each of them shows a spinner in place of its toggle icon until its key is taken off the list. | |
"basic", "stepper" | 'basic' | Tree view mode: 'basic' or 'stepper'. | |
boolean | false | If true, shows a "Select All" checkbox above the tree items. Only effective when selectable is true. | |
string | 'SelectAll' | Label for the "Select All" checkbox row. | |
boolean | false | If true, enables checkbox selection for tree items. | |
"all", "leaf" | 'all' | Selection strategy for checkboxes: all: selecting a node selects the node itself and all descendants leaf: selecting a node selects only leaf descendants (and leaf itself if it is a leaf) | |
boolean | true | Show/hide the badge for children count on directories. | |
boolean | true | Show/hide the pointer icon for selected items. | |
boolean | true | Show/hide badges with zero count. Default is true. When false, badges with 0 count will be hidden (works for both selected count and children count). | |
"base", "large", "small" | 'base' | Tree view size: 'large', 'base' or 'small'. | |
CSSStyleProperties | null | The style attribute of column element for stepper mode | |
"icon", "item" | 'item' | Determines which part of a branch item toggles its expanded state | |
"basic", "divided", "light" | 'basic' | Tree view type: 'basic', 'divided', or 'light'. | |
string[] | null | The value of the selected tree item. |
Events
| Name | Description |
|---|---|
| tk-change | Event emitted when the selected value changes. |
| tk-expand-change | Event emitted when the expanded paths change in controlled mode. Emits an array of keys (e.g., ["4", "13"]) representing the expanded items. Only the keys of expanded items are emitted, not full paths. |
| tk-item-click | Event emitted when a tree item is clicked. |
| tk-load | Event emitted when an expanded branch needs its children. Only fires while lazy is set, and only for a branch that has no children and is listed in neither loadingKeys nor loadedKeys. |