n-tree

A powerful tree component for displaying hierarchical data with features like selection, checking, drag-and-drop, virtual scrolling, async loading, and search filtering.

SKILL.md

GitHub

n-tree Component

The n-tree component is Naive UI's comprehensive solution for displaying hierarchical data structures. It provides extensive features for tree selection, checking, searching, and manipulation.

When to Use

Use n-tree when you need to:

  • Display hierarchical data structures
  • Create file/folder browsers
  • Build organization charts
  • Implement category selectors
  • Handle drag-and-drop tree manipulation
  • Support async data loading

Basic Usage

Simple Tree

<template>
  <n-tree
    block-line
    :data="data"
    :default-expanded-keys="defaultExpandedKeys"
    selectable
  />
</template>

<script setup>
import { ref } from 'vue'

const data = ref([
  {
    key: '1',
    label: 'Root Node',
    children: [
      { key: '1-1', label: 'Child 1' },
      { key: '1-2', label: 'Child 2' }
    ]
  }
])

const defaultExpandedKeys = ref(['1'])
</script>

Tree with Checkboxes

<template>
  <n-tree
    block-line
    checkable
    :data="data"
    :default-expanded-keys="defaultExpandedKeys"
    :default-checked-keys="defaultCheckedKeys"
  />
</template>

<script setup>
const data = [
  {
    key: '1',
    label: 'Parent',
    children: [
      { key: '1-1', label: 'Child 1' },
      { key: '1-2', label: 'Child 2' }
    ]
  }
]

const defaultExpandedKeys = ['1']
const defaultCheckedKeys = ['1-1']
</script>

Cascade Selection

<template>
  <n-tree
    block-line
    cascade
    checkable
    :data="data"
    :default-expanded-keys="defaultExpandedKeys"
  />
</template>

Multiple Selection

<template>
  <n-tree
    multiple
    block-line
    :data="data"
    v-model:selected-keys="selectedKeys"
  />
</template>

<script setup>
import { ref } from 'vue'

const selectedKeys = ref([])
</script>

Custom Key and Label Fields

<template>
  <n-tree
    block-line
    :data="data"
    key-field="id"
    label-field="name"
    children-field="items"
    selectable
  />
</template>

<script setup>
const data = [
  {
    id: '1',
    name: 'Custom Key',
    items: [
      { id: '1-1', name: 'Child' }
    ]
  }
]
</script>

API Reference

Props

Name Type Default Description
accordion boolean false Whether to use accordion expand mode
allow-checking-not-loaded boolean false Allow cascade checking on not loaded nodes
allow-drop (info) => boolean - Whether to allow dropping
animated boolean true Whether to show expand animation
block-line boolean false Nodes spread out the whole row
block-node boolean false Node name spreads out the whole row
cancelable boolean true Whether node selection can be cancelled
cascade boolean false Whether to cascade checkboxes
check-strategy 'all' | 'parent' | 'child' 'all' Strategy for checked callback keys
checkable boolean false Whether to display checkboxes
checkbox-placement 'left' | 'right' 'left' Checkbox placement
checked-keys Array<string | number> undefined Checked keys (controlled)
check-on-click boolean | ((node) => boolean) false Allow click to trigger check
data Array<TreeOption> [] Tree node data
default-checked-keys Array<string | number> [] Default checked keys
default-expand-all boolean false Expand all nodes
default-expanded-keys Array<string | number> [] Default expanded keys
default-selected-keys Array<string | number> [] Default selected keys
disabled-field string 'disabled' Disabled field name in TreeOption
draggable boolean false Whether nodes can be dragged
ellipsis boolean false Whether to ellipsis overflow text
expand-on-click boolean false Whether to expand on click
expand-on-dragenter boolean true Whether to expand on dragenter
expanded-keys Array<string | number> undefined Expanded keys (controlled)
filter (pattern, node) => boolean - Filter function based on pattern
get-children (option) => unknown undefined Get children of option
indent number 24 Indent per level
indeterminate-keys Array<string | number> undefined Indeterminate keys
keyboard boolean true Whether to support keyboard
key-field string 'key' Key field in TreeOption
label-field string 'label' Label field in TreeOption
multiple boolean false Whether to allow multiple selection
node-props (info) => HTMLAttributes undefined HTML attributes of node
on-load (node) => Promise<void> undefined Async load callback
override-default-node-click-behavior (info) => string undefined Override default click behavior
pattern string '' Search pattern
render-label (info) => VNodeChild undefined Render function for label
render-prefix (info) => VNodeChild undefined Render function for prefix
render-suffix (info) => VNodeChild undefined Render function for suffix
render-switcher-icon (props) => VNodeChild undefined Render function for switcher icon
scrollbar-props ScrollbarProps undefined Scrollbar props
selectable boolean true Whether nodes can be selected
selected-keys Array<string | number> undefined Selected keys (controlled)
show-irrelevant-nodes boolean true Show unmatched nodes in filter mode
show-line boolean false Whether to show connection line
spin-props object undefined Loading icon properties
virtual-scroll boolean false Whether to enable virtual scroll
watch-props Array<string> undefined Props to watch for updates

Events

Name Parameters Description
update:checked-keys (keys, options, meta) Emitted when checked keys change
update:expanded-keys (keys, options, meta) Emitted when expanded keys change
update:selected-keys (keys, options, meta) Emitted when selected keys change
update:indeterminate-keys (keys, options) Emitted when indeterminate keys change
dragstart ({ node, event }) Emitted when drag starts
dragend ({ node, event }) Emitted when drag ends
dragenter ({ node, event }) Emitted on dragenter
dragleave ({ node, event }) Emitted on dragleave
drop ({ node, dragNode, dropPosition, event }) Emitted on drop

TreeOption Properties

Name Type Description
key string | number Unique key of the node
label string Display label
checkboxDisabled boolean Whether checkbox is disabled
children TreeOption[] Child nodes
disabled boolean Whether node is disabled
isLeaf boolean Whether node is a leaf (for async loading)
prefix string | (() => VNodeChild) Prefix content
suffix string | (() => VNodeChild) Suffix content

Slots

Name Parameters Description
empty () Empty state slot

Methods

Name Parameters Description
scrollTo ScrollTo Scroll to a node in virtual scroll mode
getCheckedData () Get checked data
getIndeterminateData () Get indeterminate data

Common Patterns

Search/Filter

<template>
  <n-space vertical>
    <n-input v-model:value="pattern" placeholder="Search" />
    <n-tree
      :pattern="pattern"
      :data="data"
      block-line
    />
  </n-space>
</template>

<script setup>
import { ref } from 'vue'

const pattern = ref('')
</script>

Async Loading

<template>
  <n-tree
    block-line
    :data="data"
    :on-load="handleLoad"
    :expanded-keys="expandedKeys"
    @update:expanded-keys="handleExpandedChange"
  />
</template>

<script setup>
import { ref } from 'vue'

const data = ref([
  { key: '1', label: 'Root', isLeaf: false }
])

const expandedKeys = ref([])

const handleLoad = async (node) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      node.children = [
        { key: `${node.key}-1`, label: 'Loaded Child' }
      ]
      resolve()
    }, 1000)
  })
}

const handleExpandedChange = (keys) => {
  expandedKeys.value = keys
}
</script>

Drag and Drop

<template>
  <n-tree
    block-line
    draggable
    :data="data"
    @drop="handleDrop"
  />
</template>

<script setup>
import { ref } from 'vue'

const data = ref([
  { key: '1', label: 'Node 1', children: [] },
  { key: '2', label: 'Node 2', children: [] }
])

const handleDrop = ({ node, dragNode, dropPosition }) => {
  // Handle drop logic
}
</script>

Virtual Scroll for Large Data

<template>
  <n-tree
    block-line
    :data="largeData"
    default-expand-all
    virtual-scroll
    style="height: 400px"
  />
</template>

<script setup>
const largeData = Array.from({ length: 1000 }, (_, i) => ({
  key: `${i}`,
  label: `Node ${i}`,
  children: Array.from({ length: 5 }, (_, j) => ({
    key: `${i}-${j}`,
    label: `Child ${i}-${j}`
  }))
}))
</script>

Custom Render Functions

<template>
  <n-tree
    block-line
    :data="data"
    :render-prefix="renderPrefix"
    :render-label="renderLabel"
    :render-suffix="renderSuffix"
  />
</template>

<script setup>
import { h } from 'vue'
import { NIcon, NButton } from 'naive-ui'

const renderPrefix = ({ option }) => {
  return h(NIcon, null, { default: () => '📁' })
}

const renderLabel = ({ option }) => {
  return h('span', { style: { fontWeight: 'bold' } }, option.label)
}

const renderSuffix = ({ option }) => {
  return h(NButton, { size: 'tiny', text: true }, { default: () => 'Edit' })
}
</script>

File Tree

<template>
  <n-tree
    block-line
    expand-on-click
    :data="fileData"
    :render-switcher-icon="renderSwitcherIcon"
  />
</template>

<script setup>
import { h } from 'vue'

const fileData = [
  {
    key: 'src',
    label: 'src',
    isLeaf: false,
    children: [
      { key: 'src/main.js', label: 'main.js', isLeaf: true },
      { key: 'src/App.vue', label: 'App.vue', isLeaf: true }
    ]
  }
]

const renderSwitcherIcon = ({ option, expanded }) => {
  if (option.isLeaf) return null
  return h('span', expanded ? '📂' : '📁')
}
</script>

Context Menu

<template>
  <n-tree
    block-line
    :data="data"
    :node-props="nodeProps"
  />
  <n-dropdown
    trigger="manual"
    :show="showDropdown"
    :options="dropdownOptions"
    :x="x"
    :y="y"
    @select="handleDropdownSelect"
    @clickoutside="showDropdown = false"
  />
</template>

<script setup>
import { ref } from 'vue'

const showDropdown = ref(false)
const x = ref(0)
const y = ref(0)
const currentNode = ref(null)

const nodeProps = ({ option }) => ({
  onClick: (e) => {
    e.preventDefault()
    currentNode.value = option
    showDropdown.value = true
    x.value = e.clientX
    y.value = e.clientY
  },
  onContextmenu: (e) => {
    e.preventDefault()
    currentNode.value = option
    showDropdown.value = true
    x.value = e.clientX
    y.value = e.clientY
  }
})

const dropdownOptions = [
  { label: 'Add Child', key: 'add' },
  { label: 'Edit', key: 'edit' },
  { label: 'Delete', key: 'delete' }
]

const handleDropdownSelect = (key) => {
  showDropdown.value = false
  // Handle action
}
</script>

Best Practices

1. Always Define Unique Keys

const data = [
  { key: 'unique-id-1', label: 'Node 1' },
  { key: 'unique-id-2', label: 'Node 2' }
]

2. Use Virtual Scroll for Large Trees

<n-tree
  :data="largeData"
  virtual-scroll
  style="height: 400px"
/>

3. Use Controlled Mode for Dynamic Data

<n-tree
  :expanded-keys="expandedKeys"
  :checked-keys="checkedKeys"
  :selected-keys="selectedKeys"
  @update:expanded-keys="handleExpandedChange"
  @update:checked-keys="handleCheckedChange"
  @update:selected-keys="handleSelectedChange"
/>

4. Set isLeaf for Async Loading

const data = [
  { key: '1', label: 'Folder', isLeaf: false },  // Will show expand icon
  { key: '2', label: 'File', isLeaf: true }      // No expand icon
]

5. Use block-line for Better Click Area

<n-tree block-line :data="data" />

6. Handle Empty States

<n-tree :data="data">
  <template #empty>
    <n-empty description="No data available" />
  </template>
</n-tree>