New Jan 13, 2026

Keyboard navigation in El-Tree

Libraries, Frameworks, etc. All from Newest questions tagged vue.js - Stack Overflow View Keyboard navigation in El-Tree on stackoverflow.com

I am trying to make use of the Vue content library Element Plus, more specifically of its El-Tree component, but I need to make it functional with a keyboard and this has proven more challenging than it should be. On the Element Plus website El-Tree is completely keyboard-accessible from the start, but installed on my own system (version 2.12.0) there seems to be no keyboard functionality whatsoever.

I attempted to set up keyboard functionality with the aid of Stack Overflow's AI assist, but even it seems to rely on the existence of native functions that don't seem to actually be available, specifically expandNode and collapseNode. Instead the nodes simply open their children when I navigate down the tree, presumably because the mapping includes the child nodes from the offset.

What I'm trying to accomplish is, allow expandNode and collapseNode to work with keyboard commands and to prevent the the browser focus to follow the checkboxes separately from the el-tree's own focus.

Here's the code I'm trying to work on:

<template>
  <div ref="wrap" tabindex="0" @keydown="onKeydown">
    <el-tree
      ref="treeRef"
      :data="data"
      :props="defaultProps"
      node-key="id"
      show-checkbox
    />
  </div>
</template>

<script> export default { data() { return { defaultProps: { children: 'children', label: 'label' }, data: [ const data = [ { id: 1, label: 'Level one 1', children: [ { id: 4, label: 'Level two 1-1', children: [ { id: 9, label: 'Level three 1-1-1', }, { id: 10, label: 'Level three 1-1-2', }, ], }, ], }, { id: 2, label: 'Level one 2', children: [ { id: 5, label: 'Level two 2-1', }, { id: 6, label: 'Level two 2-2', }, ], }, { id: 3, label: 'Level one 3', children: [ { id: 7, label: 'Level two 3-1', }, { id: 8, label: 'Level two 3-2', }, ], }, ] ] } }, mounted() { this.$refs.wrap && this.$refs.wrap.focus() }, methods: { buildVisibleList() { const visible = [] const traverse = (nodes, parent = null) => { if (!nodes) return for (const node of nodes) { visible.push({ key: node.id, data: node, parent }) if (node.children && node.children.length) traverse(node.children, node) } } traverse(this.data) return visible }, focusNodeByIndex(list, idx) { const item = list[idx] if (!item) return const tree = this.$refs.treeRef if (!tree) return tree.setCurrentKey(item.key) if (item.parent) tree.expandNode(item.parent, true) }, onKeydown(e) { const tree = this.$refs.treeRef if (!tree) return const visible = this.buildVisibleList() const currentKey = tree.getCurrentKey() const idx = visible.findIndex(n => n.key === currentKey)

if (e.key === 'ArrowDown') { e.preventDefault() this.focusNodeByIndex(visible, (idx >= 0 ? idx + 1 : 0)) } else if (e.key === 'ArrowUp') { e.preventDefault() this.focusNodeByIndex(visible, (idx > 0 ? idx - 1 : 0)) } else if (e.key === 'ArrowRight') { e.preventDefault() const node = tree.getNode(currentKey) node && tree.expandNode(node.data, true) } else if (e.key === 'ArrowLeft') { e.preventDefault() const node = tree.getNode(currentKey) node && tree.collapseNode(node.data, true) } else if (e.key === 'Enter' || e.key === ' ') { e.preventDefault() const node = tree.getNode(currentKey) if (node) { const checked = (tree.getCheckedKeys() || []).includes(node.key) tree.setCheckedKeys(checked ? [] : [node.key], false) } } } } } </script>

Scroll to top