Skip to content

Tree ​

A hierarchical tree view component for displaying nested data with expand/collapse, selection, checkboxes, drag & drop, filtering, lazy loading, inline editing, and more.

Basic ​

Click on a branch node's arrow to expand or collapse it.

Documents
Work
Reports.xlsx
Presentation.pptx
Personal
Resume.pdf
Budget.xlsx
Pictures
Vacation
beach.jpg
sunset.jpg
Screenshots
bug-report.png
Music
playlist.m3u
favorites.m3u
vue
<script setup>
import { VxTree } from 'veloxui'

const data = [
  {
    key: '1', label: 'Documents',
    children: [
      { key: '1-1', label: 'Work',
        children: [
          { key: '1-1-1', label: 'Reports.xlsx' },
          { key: '1-1-2', label: 'Presentation.pptx' },
        ],
      },
      { key: '1-2', label: 'Personal',
        children: [
          { key: '1-2-1', label: 'Resume.pdf' },
        ],
      },
    ],
  },
]
</script>

<template>
  <VxTree :data="data" />
</template>

Selection ​

Use v-model:selected to bind the selected node key. Enabled by default.

Selected: none

Documents
Work
Reports.xlsx
Presentation.pptx
Personal
Resume.pdf
Budget.xlsx
Pictures
Vacation
beach.jpg
sunset.jpg
Screenshots
bug-report.png
Music
playlist.m3u
favorites.m3u
vue
<script setup>
import { ref } from 'vue'
const selectedKey = ref(null)
</script>

<template>
  <VxTree :data="data" v-model:selected="selectedKey" />
</template>

Multiple Selection ​

Enable multiple to select more than one node. Hold Ctrl (or Cmd on Mac) and click to toggle individual nodes.

Selected: none

Documents
Work
Reports.xlsx
Presentation.pptx
Personal
Resume.pdf
Budget.xlsx
Pictures
Vacation
beach.jpg
sunset.jpg
Screenshots
bug-report.png
Music
playlist.m3u
favorites.m3u
vue
<script setup>
import { ref } from 'vue'
const selected = ref([])
</script>

<template>
  <VxTree :data="data" v-model:selected="selected" multiple default-expand-all />
</template>

Checkboxes ​

Add checkboxes with checkable. Parent nodes show indeterminate state when partially checked.

Checked: none

Frontend
Vue
React
Angular
Backend
Node.js
Python
Go
Database
PostgreSQL
MySQL
MongoDB
vue
<script setup>
import { ref } from 'vue'
const checkedKeys = ref([])
</script>

<template>
  <VxTree :data="data" checkable v-model:checked="checkedKeys" default-expand-all />
</template>

Show Lines ​

Display connecting lines with show-line. Use line-style to choose between solid, dashed, or dotted.

Solid
src
components
Button.vue
Input.vue
Modal.vue
utils
helpers.ts
constants.ts
App.vue
main.ts
public
favicon.ico
package.json
tsconfig.json
Dashed
src
components
Button.vue
Input.vue
Modal.vue
utils
helpers.ts
constants.ts
App.vue
main.ts
public
favicon.ico
package.json
tsconfig.json
Dotted
src
components
Button.vue
Input.vue
Modal.vue
utils
helpers.ts
constants.ts
App.vue
main.ts
public
favicon.ico
package.json
tsconfig.json
vue
<template>
  <VxTree :data="data" show-line default-expand-all />
  <VxTree :data="data" show-line line-style="dashed" default-expand-all />
  <VxTree :data="data" show-line line-style="dotted" default-expand-all />
</template>

Filter with Highlight ​

Use filter-text to filter visible nodes. Matching text is highlighted automatically. The tree auto-expands to show matching children and restores previous state when the filter is cleared.

Animals
Mammals
Dog
Cat
Elephant
Birds
Eagle
Parrot
Fish
Salmon
Tuna
Plants
Trees
Oak
Pine
Flowers
Rose
Tulip
vue
<script setup>
import { ref } from 'vue'
const filterText = ref('')
</script>

<template>
  <VxInput v-model="filterText" placeholder="Type to filter..." />
  <VxTree :data="data" :filter-text="filterText" default-expand-all />
</template>

Default Expand All ​

Use default-expand-all to expand every branch on mount.

Documents
Work
Reports.xlsx
Presentation.pptx
Personal
Resume.pdf
Budget.xlsx
Pictures
Vacation
beach.jpg
sunset.jpg
Screenshots
bug-report.png
Music
playlist.m3u
favorites.m3u

Accordion ​

Only one node per level can be expanded at a time. Expanding a node collapses its siblings.

Fruits
Apple
Banana
Orange
Vegetables
Carrot
Broccoli
Spinach
Grains
Rice
Wheat
vue
<template>
  <VxTree :data="data" accordion />
</template>

Node Icons ​

Use the icon property on node data, or customize with the #icon slot.

📁src
📄index.ts
🎨styles.css
📁utils
📄helpers.ts
📦package.json
🔒.gitignore
vue
<script setup>
const data = [
  {
    key: 'src', label: 'src', icon: '📁',
    children: [
      { key: 'index', label: 'index.ts', icon: '📄' },
      { key: 'styles', label: 'styles.css', icon: '🎨' },
    ],
  },
  { key: 'pkg', label: 'package.json', icon: '📦' },
]
</script>

<template>
  <VxTree :data="data" default-expand-all />
</template>

Use the #icon slot for full control:

vue
<template>
  <VxTree :data="data">
    <template #icon="{ node, expanded }">
      <MyIcon :name="node.children ? (expanded ? 'folder-open' : 'folder') : 'file'" />
    </template>
  </VxTree>
</template>

Children Count ​

Show a badge with the number of children using show-count. Customize with the #count slot.

Components2
Basic3
Button
Input
Badge
Form2
Select
Checkbox
Utilities4
useClickOutside
useDebounce
useThrottle
useMediaQuery
vue
<template>
  <VxTree :data="data" show-count default-expand-all />
</template>

Node Actions ​

Use the #actions slot to add hover-visible action buttons to each node.

Workspace
Notes
Tasks
Calendar
Archive
Shared
vue
<template>
  <VxTree :data="data" default-expand-all>
    <template #actions="{ node }">
      <button @click="editNode(node)">Edit</button>
      <button @click="deleteNode(node)">Delete</button>
    </template>
  </VxTree>
</template>

Drag & Drop ​

Enable draggable to allow reordering nodes. The node-drop event provides dragNode, dropNode, and position (before, inside, or after) for you to handle the data update.

Design System
Colors
Typography
Spacing
Components
Button
Input
Modal
Utilities
Helpers
Hooks
vue
<script setup>
import { ref } from 'vue'

const data = ref([...])

function onDrop(info) {
  console.log(info.dragNode.label, info.position, info.dropNode.label)
  // Handle data mutation here
}
</script>

<template>
  <VxTree :data="data" draggable @node-drop="onDrop" />
</template>

Customize the drag handle with the #drag-handle slot:

vue
<template>
  <VxTree :data="data" draggable>
    <template #drag-handle="{ node }">
      <MyGripIcon />
    </template>
  </VxTree>
</template>

Lazy Loading ​

Pass a load function to fetch children on demand. Just provide your data and a loader — the tree handles the rest:

  • A spinner shows while loading
  • Empty results auto-mark the node as a leaf (no more load attempts)
  • Errors show inline with a Retry button
  • Already-loaded nodes are cached — expanding again won't re-fetch
  • Use isLeaf: true on nodes you know are leaves to skip the load entirely

Basic ​

Click the arrows to load children on demand. "Empty Folder" returns no results and becomes a leaf.

Folder A
Folder B
Empty Folder
readme.txt
vue
<script setup>
import { ref } from 'vue'

const data = ref([
  { key: 'folder-a', label: 'Folder A' },
  { key: 'folder-b', label: 'Folder B' },
  { key: 'empty', label: 'Empty Folder' },
  { key: 'readme', label: 'readme.txt', isLeaf: true },
])

async function load(node) {
  const res = await fetch(`/api/${node.key}/children`)
  return await res.json() // return TreeNodeData[]
}
</script>

<template>
  <VxTree :data="data" :load="load" />
</template>

Error Handling & Retry ​

If load throws, the tree shows the error message with a retry button. Click "Orders" below to see the error state.

Users
Products
Orders
vue
<script setup>
async function load(node) {
  const res = await fetch(`/api/${node.key}/children`)
  if (!res.ok) throw new Error('Failed to fetch')
  return await res.json()
}
</script>

<template>
  <!-- Errors show inline with a Retry button -->
  <VxTree :data="data" :load="load" />
</template>

Editable Labels ​

Enable editable to allow inline renaming. Double-click a label to edit it. Press Enter to save, Escape to cancel. Handle the node-edit event to apply changes.

Double-click a label to rename it

Project Alpha
Design
Development
Testing
Project Beta
Research
Prototype
vue
<script setup>
function onEdit({ node, newLabel, oldLabel }) {
  node.label = newLabel  // apply the change
}
</script>

<template>
  <VxTree :data="data" editable @node-edit="onEdit" />
</template>

Context Menu ​

Use the #context-menu slot to add a right-click menu. The slot provides node and close.

Right-click any node to see the context menu

Home
Dashboard
Analytics
Settings
Profile
Security
Help
vue
<template>
  <VxTree :data="data">
    <template #context-menu="{ node, close }">
      <div class="menu">
        <button @click="openNode(node); close()">Open</button>
        <button @click="renameNode(node); close()">Rename</button>
        <button @click="deleteNode(node); close()">Delete</button>
      </div>
    </template>
  </VxTree>
</template>

Striped Rows ​

Add striped for alternating row backgrounds to improve readability.

Category A
Item 1
Item 2
Item 3
Category B
Item 4
Item 5
Category C
Item 6
vue
<template>
  <VxTree :data="data" striped default-expand-all />
</template>

Virtual Scrolling ​

For large datasets, enable virtual to only render visible nodes. Set virtual-height for the container height and item-height for each row.

50 categories × 20 items = 1,000 nodes

Category 1
Item 1-1
Item 1-2
Item 1-3
Item 1-4
Item 1-5
Item 1-6
Item 1-7
Item 1-8
Item 1-9
Item 1-10
Item 1-11
Item 1-12
Item 1-13
Item 1-14
Item 1-15
Item 1-16
Item 1-17
Item 1-18
Item 1-19
vue
<script setup>
// Generate 1,000 nodes
const data = Array.from({ length: 50 }, (_, i) => ({
  key: `cat-${i}`,
  label: `Category ${i + 1}`,
  children: Array.from({ length: 20 }, (_, j) => ({
    key: `cat-${i}-item-${j}`,
    label: `Item ${i + 1}-${j + 1}`,
    isLeaf: true,
  })),
}))
</script>

<template>
  <VxTree :data="data" virtual :virtual-height="300" :item-height="30" default-expand-all />
</template>

Disabled Nodes ​

Nodes with disabled: true cannot be selected or checked.

Available Modules
Authentication
Dashboard
Locked Modules
Admin Panel
Billing

Custom Content ​

Use the default slot for fully custom node rendering. Receives node, expanded, selected, and checkState.

📂 Inbox
📄 Starred
📄 Important
📄 Drafts
📂 Sent
📄 Today
📄 This Week
🗑️ Trash
⛔ Spam
vue
<template>
  <VxTree :data="data" default-expand-all>
    <template #default="{ node, expanded }">
      <span>{{ node.children ? (expanded ? '📂' : '📁') : '📄' }} {{ node.label }}</span>
    </template>
  </VxTree>
</template>

API ​

VxTree Props ​

PropTypeDefaultDescription
dataTreeNodeData[][]Tree data
selectedstring | number | null | arraynullSelected key(s) (v-model)
checked(string | number)[][]Checked keys (v-model)
expanded(string | number)[]—Expanded keys (v-model)
default-expand-allbooleanfalseExpand all on mount
default-expanded-keys(string | number)[][]Keys to expand on mount
checkablebooleanfalseShow checkboxes
selectablebooleantrueEnable selection
multiplebooleanfalseAllow multi-selection (Ctrl/Cmd+Click)
show-linebooleanfalseShow connecting lines
line-style'solid' | 'dashed' | 'dotted''solid'Line style
accordionbooleanfalseOne node per level
indentnumber24Indent per level (px)
filter-textstring''Filter by label
highlight-filterbooleantrueHighlight matched filter text
draggablebooleanfalseEnable drag & drop
editablebooleanfalseEnable double-click rename
show-countbooleanfalseShow children count badge
stripedbooleanfalseAlternating row backgrounds
load(node) => Promise<TreeNodeData[]>—Async children loader
virtualbooleanfalseEnable virtual scrolling
virtual-heightnumber400Virtual container height (px)
item-heightnumber30Virtual item height (px)

TreeNodeData ​

PropertyTypeRequiredDescription
keystring | numberYesUnique identifier
labelstringYesDisplay text
childrenTreeNodeData[]NoChild nodes
disabledbooleanNoDisable interaction
isLeafbooleanNoForce leaf (skip lazy load)
iconstringNoNode icon (emoji or text)

Events ​

EventPayloadDescription
update:selectedkey | keys[]Selection changed
update:checkedkeys[]Checked state changed
update:expandedkeys[]Expanded state changed
node-clicknodeNode clicked
node-expandnodeNode expanded
node-collapsenodeNode collapsed
node-drop{ dragNode, dropNode, position }Node dropped (drag & drop)
node-drag-startnodeDrag started
node-edit{ node, newLabel, oldLabel }Label edited
node-context-menu{ node, event }Right-click on node
node-loadednode, childrenLazy load completed

Slots ​

SlotPropsDescription
default{ node, expanded, selected, checkState }Custom node content
icon{ node, expanded }Custom node icon
actions{ node }Hover action buttons
drag-handle{ node }Custom drag handle
count{ node, count }Custom count badge
context-menu{ node, close }Right-click context menu
empty—Content when data is empty