How to Work with the Element UI Tree Component for Hierarchical Data Display and Operations

The Element UI Tree component (ElTree) renders hierarchical data recursively using ElTreeNode for individual items and TreeStore for state management, supporting lazy loading, checkboxes, drag-and-drop, and comprehensive programmatic APIs.

The ElemeFE/element repository provides a robust Tree component for Vue.js applications requiring nested data visualization. This component handles complex hierarchical relationships through a clean store-pattern architecture that separates presentation logic from data manipulation. Whether building file browsers, organization charts, or nested category trees, understanding the internal structure of ElTree, ElTreeNode, and TreeStore is essential for effective implementation.

Architecture of the Tree Component

The Tree component follows a three-layer architecture where data flows from properties through a central store into recursive node components.

Core Components

ElTree (packages/tree/src/tree.vue) serves as the root container. It accepts the data prop, instantiates a TreeStore (lines 25-38), and exposes public methods including append, remove, filter, and getCheckedKeys. This component handles drag-and-drop event coordination, keyboard navigation via handleKeydown (lines 94-119), and emits tree-level events.

ElTreeNode (packages/tree/src/tree-node.vue) renders individual nodes recursively. Each instance manages its own expansion state, checkbox visibility, and drag handles, emitting node-specific events like node-click and node-expand that bubble up to the root component.

TreeStore (packages/tree/src/model/tree-store.js) acts as the pure data layer. It maintains the canonical node hierarchy, manages selection states (checkedKeys, halfCheckedKeys), and provides all mutation methods. Every structural change flows through this store, which then notifies the UI layer to re-render.

Data Flow

  1. Props → Store: On creation, ElTree initializes TreeStore with data, props (field mappings), nodeKey, and lazy configuration.

  2. Store → Nodes: The store creates a virtual root node (this.root). The template renders root.childNodes using recursive <el-tree-node> components (lines 13-21 of tree.vue).

  3. Interaction → Store: User actions (clicks, checks, drops) or programmatic API calls forward to store methods like setChecked, remove, or insertBefore. The store updates its internal state and triggers Vue's reactivity system.

Core Features and Implementation Details

Lazy Loading for Large Hierarchies

When lazy is set to true, expanding a node triggers the load prop function. This function receives the node being expanded and a resolve callback. Once asynchronous data fetching completes, calling resolve(children) registers the new nodes in the store without reloading the entire tree. This implementation in tree-node.vue prevents memory issues with massive datasets.

Checkbox Selection and State Management

The show-checkbox prop enables checkboxes on each node. The store maintains three selection states: checked, unchecked, and indeterminate (half-checked). By default, checking a parent checks all descendants unless check-strictly is enabled. Access selection programmatically via getCheckedKeys(), getCheckedNodes(), setCheckedKeys(), or setChecked().

Drag-and-Drop with Custom Constraints

Enable native HTML5 drag-and-drop by setting draggable. The component emits node-drag-start, node-drag-over, and node-drag-end events. Customize behavior using:

  • allow-drag: Function determining if a specific node can be dragged
  • allow-drop: Function determining if a dragged node can be dropped at a specific position (before, after, or inner)

Call this.$refs.tree.filter(value) to filter the tree. This delegates to store.filter, which invokes the filterNodeMethod prop function for every node. Return true to show the node, false to hide it. The filter automatically handles parent visibility when children match.

Keyboard Navigation

The handleKeydown method (lines 94-119 of tree.vue) implements full keyboard accessibility:

  • Arrow keys navigate between nodes
  • Space or Enter toggles checkboxes
  • Left/Right arrows collapse and expand nodes

Practical Implementation Examples

Rendering a Basic Static Tree

<template>
  <el-tree
    :data="treeData"
    :props="{ children: 'children', label: 'name' }"
    :default-expand-all="true"
    @node-click="onNodeClick">
  </el-tree>
</template>

<script>
export default {
  data() {
    return {
      treeData: [
        {
          name: 'Folder A',
          children: [{ name: 'File A1' }, { name: 'File A2' }]
        },
        {
          name: 'Folder B',
          children: [{ name: 'File B1' }]
        }
      ]
    };
  },
  methods: {
    onNodeClick(nodeData, node, component) {
      console.log('Clicked:', nodeData);
    }
  }
};
</script>

Key points:

  • The :props object maps your data fields to the component's expected children and label properties
  • default-expand-all expands all nodes on mount
  • node-click emits the raw data, the internal node object, and the Vue component instance

Implementing Lazy Loading for Large Datasets

<template>
  <el-tree
    :data="treeData"
    :load="loadNode"
    lazy
    :props="{ children: 'children', label: 'title' }">
  </el-tree>
</template>

<script>
export default {
  data() {
    return {
      treeData: [{ id: 1, title: 'Root', hasChildren: true }]
    };
  },
  methods: {
    loadNode(node, resolve) {
      if (node.level === 0) return resolve(this.treeData);
      
      // Simulate async API call
      setTimeout(() => {
        const children = [
          { id: node.data.id * 10 + 1, title: `Child of ${node.data.title}` }
        ];
        resolve(children);
      }, 500);
    }
  }
};
</script>

Implementation notes:

  • Both lazy prop and load function are required
  • The resolve callback must be called with an array of child nodes
  • Leaf nodes are automatically detected when resolve([]) is called with an empty array

Managing Node Selection and Checkboxes

<template>
  <div>
    <el-tree
      :data="treeData"
      show-checkbox
      node-key="id"
      :default-checked-keys="[2, 3]"
      :check-strictly="true"
      ref="myTree">
    </el-tree>
    
    <button @click="logChecked">Get Checked Keys</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      treeData: [
        { id: 1, label: 'Parent', children: [
          { id: 2, label: 'Child 1' },
          { id: 3, label: 'Child 2' }
        ]}
      ]
    };
  },
  methods: {
    logChecked() {
      // Returns array of node-key values (e.g., [2, 3])
      const keys = this.$refs.myTree.getCheckedKeys();
      console.log('Checked keys:', keys);
      
      // Returns array of node data objects
      const nodes = this.$refs.myTree.getCheckedNodes();
      console.log('Checked nodes:', nodes);
    }
  }
};
</script>

Important considerations:

  • node-key is mandatory for programmatic selection methods; it specifies which property serves as the unique identifier
  • check-strictly disables the default behavior where parent and child selections are linked
  • Use setCheckedKeys([1, 2]) to programmatically set selection

Enabling Drag-and-Drop with Custom Constraints

<template>
  <el-tree
    :data="treeData"
    draggable
    :allow-drag="canDrag"
    :allow-drop="canDrop"
    @node-drop="onDrop"
    node-key="id">
  </el-tree>
</template>

<script>
export default {
  data() {
    return {
      treeData: [
        { id: 1, label: 'Root', children: [
          { id: 2, label: 'Draggable Node' },
          { id: 3, label: 'Drop Target', children: [] }
        ]}
      ]
    };
  },
  methods: {
    canDrag(node) {
      // Prevent dragging nodes marked as disabled
      return !node.disabled;
    },
    canDrop(dragNode, dropNode, type) {
      // Type can be: 'prev' (before), 'next' (after), or 'inner' (child)
      // Prevent dropping into leaf nodes (only allow dropping before/after)
      if (type === 'inner' && dropNode.isLeaf) return false;
      return true;
    },
    onDrop(dragNode, dropNode, type, ev) {
      console.log(`Moved ${dragNode.data.label} ${type} ${dropNode.data.label}`);
      // Trigger backend sync here
    }
  }
};
</script>

Drag behavior details:

  • allow-drag receives the node object and must return a boolean
  • allow-drop receives the dragged node, target node, and drop position string
  • The node-drop event fires after the internal state updates successfully

Programmatic Tree Manipulation

<template>
  <div>
    <el-tree :data="treeData" node-key="id" ref="tree"></el-tree>
    <button @click="addNode">Add Child to Node 5</button>
    <button @click="removeNode">Remove Node 3</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      treeData: [
        { id: 1, label: 'Root', children: [
          { id: 3, label: 'Node 3' },
          { id: 5, label: 'Node 5', children: [] }
        ]}
      ]
    };
  },
  methods: {
    addNode() {
      // Append requires parent node reference, obtained via getNode(key)
      const parentNode = this.$refs.tree.getNode(5);
      this.$refs.tree.append({ id: 99, label: 'New Node' }, parentNode);
    },
    removeNode() {
      // Remove by key using getNode to resolve the node object
      this.$refs.tree.remove(this.$refs.tree.getNode(3));
    }
  }
};
</script>

API methods available:

  • append(data, parentNode) and append(data, parentKey): Add child to end
  • insertBefore(data, refNode) and insertAfter(data, refNode): Insert at specific position
  • remove(node): Delete node and all children
  • getNode(key): Retrieve internal node object by its key value

Key Source Files and References

Understanding the source structure enables advanced customization and debugging:

Summary

  • Store-pattern architecture separates ElTree (UI), ElTreeNode (recursive rendering), and TreeStore (data manipulation) for maintainable hierarchical data management
  • Lazy loading requires both the lazy prop and a load function that calls resolve(children) to populate nodes on demand without loading the entire dataset
  • Programmatic operations depend on the node-key prop to uniquely identify nodes for methods like getNode, append, remove, and setCheckedKeys
  • Drag-and-drop uses native HTML5 events with allow-drag and allow-drop callbacks to implement business logic constraints
  • Filtering leverages filterNodeMethod to determine visibility, called via the filter(value) method on the component instance

Frequently Asked Questions

How does the Tree component handle large datasets efficiently?

The Tree component implements lazy loading through the lazy prop and load function. When enabled, child nodes load only when a parent expands, preventing memory and performance issues with massive trees. The TreeStore maintains only loaded nodes in memory, and the load function receives a resolve callback to register new children asynchronously without re-rendering the entire tree.

What is the difference between node-key and the standard key prop?

The node-key prop specifies which property in your data objects serves as the unique identifier (e.g., id or uuid). This is required for all programmatic operations like getNode, setCheckedKeys, and remove. The standard Vue key prop is used internally for list rendering optimization, while node-key is specific to the Tree component's data model and API methods.

How can I filter or search nodes in the Tree component?

Call this.$refs.tree.filter(searchValue) where searchValue is passed to your filterNodeMethod function. This function receives two arguments: value (the search string) and data (the node's data object), and must return boolean to determine visibility. The filter automatically handles parent visibility when children match, ensuring the tree structure remains intact while hiding non-matching branches.

Is drag-and-drop supported between different Tree instances?

No, the Element UI Tree component does not support cross-tree dragging natively. The drag event handlers in tree-node.vue scope drag operations to a single ElTree instance. To implement cross-tree dragging, you would need to manually coordinate between instances using the node-drag-start, node-drag-end, and node-drop events, manually removing nodes from the source tree and appending them to the destination tree using the programmatic API.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →