Skip to content
API 风格:

Tree ​

Flat-to-tree conversion and check propagation live in pure core functions; the component only renders and handles the keyboard.

Basic usage ​

Basic
Frontend
Vue
React
Ops
vue
<script setup>
import { ref } from 'vue'
const treeData = [
  { key: 'a', label: 'Frontend', children: [
    { key: 'a-1', label: 'Vue' },
    { key: 'a-2', label: 'React' },
  ] },
  { key: 'b', label: 'Backend', children: [{ key: 'b-1', label: 'Node' }] },
  { key: 'c', label: 'Ops', disabled: true },
]
</script>

<template>
  <!-- Basic usage -->
  <LhTree :data="treeData" :default-expanded-keys="['a']" />
</template>
vue
<script>
export default {
  data() {
    return {
      treeData: [
        { key: 'a', label: 'Frontend', children: [
          { key: 'a-1', label: 'Vue' },
          { key: 'a-2', label: 'React' },
        ] },
        { key: 'b', label: 'Backend', children: [{ key: 'b-1', label: 'Node' }] },
        { key: 'c', label: 'Ops', disabled: true },
      ],
    }
  },
}
</script>

<template>
  <!-- Basic usage -->
  <LhTree :data="treeData" :default-expanded-keys="['a']" />
</template>

Checkable (cascading) ​

Checking a parent cascades to all descendants; a fully checked subtree marks the parent checked, a partially checked one shows indeterminate.

Checkable
Frontend
Vue
React
Backend
Node
Ops
vue
<script setup lang="ts">
const treeData = [
  { key: 'a', label: 'Frontend', children: [
    { key: 'a-1', label: 'Vue' },
    { key: 'a-2', label: 'React' },
  ] },
  { key: 'b', label: 'Backend', children: [{ key: 'b-1', label: 'Node' }] },
  { key: 'c', label: 'Ops', disabled: true },
]
</script>

<template>
  <!-- Checkable -->
  <LhTree :data="treeData" show-checkbox :default-expanded-keys="['a', 'b']" />
</template>
vue
<script>
export default {
  data() {
    return {
      treeData: [
        { key: 'a', label: 'Frontend', children: [
          { key: 'a-1', label: 'Vue' },
          { key: 'a-2', label: 'React' },
        ] },
        { key: 'b', label: 'Backend', children: [{ key: 'b-1', label: 'Node' }] },
        { key: 'c', label: 'Ops', disabled: true },
      ],
    }
  },
}
</script>

<template>
  <!-- Checkable -->
  <LhTree :data="treeData" show-checkbox :default-expanded-keys="['a', 'b']" />
</template>

Lazy loading ​

With lazy on, expanding an unloaded node calls the load callback (resolve commits the children and expands, reject cancels); the arrow shows a spinning state while loading. Loadable nodes must declare isLeaf: false explicitly (a node without children is otherwise inferred as a leaf and shows no expand arrow); loaded branches that can load further also declare isLeaf: false, real leaves declare isLeaf: true.

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

const lazyData = ref([
  { key: 'lazy', label: 'Click to load', isLeaf: false },
])

function loadNodes(node, resolve, reject) {
  // simulate an async request
  setTimeout(() => {
    if (node.key === 'lazy') {
      resolve([
        { key: 'lazy-1', label: 'Child one', isLeaf: true },
        { key: 'lazy-2', label: 'Child two', isLeaf: true },
      ])
    } else {
      reject(new Error('nothing to load'))
    }
  }, 600)
}
</script>

<template>
  <!-- Lazy load -->
  <LhTree :data="lazyData" lazy :load="loadNodes" />
</template>
vue
<script>
export default {
  data() {
    return {
      lazyData: [{ key: 'lazy', label: 'Click to load', isLeaf: false }],
    }
  },
  methods: {
    // simulate an async request
    loadNodes(node, resolve, reject) {
      setTimeout(() => {
        if (node.key === 'lazy') {
          resolve([
            { key: 'lazy-1', label: 'Child one', isLeaf: true },
            { key: 'lazy-2', label: 'Child two', isLeaf: true },
          ])
        } else {
          reject(new Error('nothing to load'))
        }
      }, 600)
    },
  },
}
</script>

<template>
  <!-- Lazy load -->
  <LhTree :data="lazyData" lazy :load="loadNodes" />
</template>

Detached checks (checkStrictly) ​

With checkStrictly on, checking affects only the node itself: no cascade to descendants, no ancestor backtracking, no indeterminate state.

checkStrictly
Frontend
Vue
React
Backend
Node
Ops
vue
<script setup lang="ts">
const treeData = [
  { key: 'a', label: 'Frontend', children: [
    { key: 'a-1', label: 'Vue' },
    { key: 'a-2', label: 'React' },
  ] },
  { key: 'b', label: 'Backend', children: [{ key: 'b-1', label: 'Node' }] },
  { key: 'c', label: 'Ops', disabled: true },
]
</script>

<template>
  <!-- checkStrictly -->
  <LhTree :data="treeData" show-checkbox check-strictly :default-expanded-keys="['a', 'b']" />
</template>
vue
<script>
export default {
  data() {
    return {
      treeData: [
        { key: 'a', label: 'Frontend', children: [
          { key: 'a-1', label: 'Vue' },
          { key: 'a-2', label: 'React' },
        ] },
        { key: 'b', label: 'Backend', children: [{ key: 'b-1', label: 'Node' }] },
        { key: 'c', label: 'Ops', disabled: true },
      ],
    }
  },
}
</script>

<template>
  <!-- checkStrictly -->
  <LhTree :data="treeData" show-checkbox check-strictly :default-expanded-keys="['a', 'b']" />
</template>

Selection and controlled keys ​

multiple enables multi-select; expand-on-click-node toggles nodes on click; default-selected-keys / v-model:selected-keys control selection; default-checked-keys / v-model:checked-keys control checking; default-expanded-keys / v-model:expanded-keys control expansion; disabled disables the whole tree.

Controlled selection & checking
Design
Color
Type
Engineering

Selected: [ "x" ] | Checked: [ "x-1" ]

Disable the whole tree
Engineering
vue
<script setup>
import { ref } from 'vue'
const selKeys = ref(['x'])
const chkKeys = ref(['x-1'])
const ctrlData = [
  { key: 'x', label: 'Design', children: [
    { key: 'x-1', label: 'Color' },
    { key: 'x-2', label: 'Type' },
  ] },
  { key: 'y', label: 'Engineering' },
]
</script>

<template>
  <!-- Controlled selection & checking -->
  <LhTree
    v-model:selected-keys="selKeys"
    v-model:checked-keys="chkKeys"
    :data="ctrlData"
    multiple
    expand-on-click-node
    :default-expanded-keys="['x']"
    :default-selected-keys="['x']"
    :default-checked-keys="['x-1']"
  />
  <p class="demo-tree-log">Selected: {{ selKeys }} | Checked: {{ chkKeys }}</p>
  <!-- Disable the whole tree -->
  <LhTree :data="ctrlData" disabled />
</template>
vue
<script>
export default {
  data() {
    return {
      selKeys: ['x'],
      chkKeys: ['x-1'],
      ctrlData: [
        { key: 'x', label: 'Design', children: [
          { key: 'x-1', label: 'Color' },
          { key: 'x-2', label: 'Type' },
        ] },
        { key: 'y', label: 'Engineering' },
      ],
    }
  },
}
</script>

<template>
  <!-- Controlled selection & checking -->
  <LhTree
    v-model:selected-keys="selKeys"
    v-model:checked-keys="chkKeys"
    :data="ctrlData"
    multiple
    expand-on-click-node
    :default-expanded-keys="['x']"
    :default-selected-keys="['x']"
    :default-checked-keys="['x-1']"
  />
  <p class="demo-tree-log">Selected: {{ selKeys }} | Checked: {{ chkKeys }}</p>
  <!-- Disable the whole tree -->
  <LhTree :data="ctrlData" disabled />
</template>

API ​

LhTree Props ​

AttributeDescriptionTypeDefault
dataTree data[LhTreeNode](#type-LhTreeNode)[]—
showCheckboxShow checkboxesbooleanfalse
multipleMulti-select (toggle instead of replace)booleanfalse
expandOnClickNodeExpand/collapse non-leaf nodes on clickbooleantrue
defaultExpandedKeysDefault expanded keys (uncontrolled)string[]() => []
expandedKeysControlled expanded keysstring[]—
defaultSelectedKeysDefault selected keys (uncontrolled)string[]() => []
selectedKeysControlled selected keysstring[]—
defaultCheckedKeysDefault checked keys (uncontrolled)string[]() => []
checkedKeysControlled checked keysstring[]—
checkStrictlyDetached checks (no cascade, no indeterminate)booleanfalse
lazyLazy mode (calls load when expanding unloaded nodes)booleanfalse
loadLazy load callback (resolve commits children and expands, reject cancels)(node: LhTreeNode, resolve: (children: LhTreeNode[]) => void, reject: (reason?: unknown) => void) => void—
disabledDisable the whole treebooleanfalse
sxQuick layout propsLhSxProps—

LhTree Events ​

EventDescriptionCallback
update:expandedKeysEmitted when the expanded keys change (use with v-model:expanded-keys)(keys: string[]) => void
update:selectedKeysEmitted when the selected keys change (use with v-model:selected-keys)(keys: string[]) => void
update:checkedKeysEmitted when the checked keys change (use with v-model:checked-keys)(keys: string[]) => void
selectEmitted after a node is clicked (selected / toggled)(key: string, node: LhTreeNode) => void
checkEmitted after the checked state changes(keys: string[], node: LhTreeNode) => void
expandEmitted after a node expands / collapses(key: string, expanded: boolean) => void
nodeLoadEmitted after lazy-loaded children arrive(node: LhTreeNode, children: LhTreeNode[]) => void

Slots: node (scope { node, level, expanded, selected }), empty.

LhTreeNode ​

FieldDescriptionType
keyUnique keystring
labelDisplay textstring
childrenChild nodesLhTreeNode[]
disabledDisabledboolean
isLeafForce leaf (inferred from children otherwise)boolean

Accessibility ​

  • role="tree" + role="treeitem" with aria-level / aria-expanded / aria-selected / aria-disabled
  • Checkboxes are role="checkbox" with aria-checked (mixed for indeterminate)
  • Keyboard (APG tree pattern): ↑/↓ move (skipping disabled, wrapping), → expand or enter first child, ← collapse or go to parent, Home/End, Enter/Space activate; roving tabindex

Type Definitions ​

The TypeScript type structures exported by this component (auto-extracted from packages/components/tree/src/types.ts and @lh-ui/share/core/* sources; click each entry to expand; see the Props table above for attribute descriptions):

LhTreeProps
ts
export interface LhTreeProps {
  data: LhTreeNode[]
  showCheckbox?: boolean
  multiple?: boolean
  expandOnClickNode?: boolean
  defaultExpandedKeys?: string[]
  expandedKeys?: string[]
  defaultSelectedKeys?: string[]
  selectedKeys?: string[]
  defaultCheckedKeys?: string[]
  checkedKeys?: string[]
  checkStrictly?: boolean
  lazy?: boolean
  load?: (
    node: LhTreeNode,
    resolve: (children: LhTreeNode[]) => void,
    reject: (reason?: unknown) => void,
  ) => void
  disabled?: boolean
  sx?: LhSxProps
}
LhFlatTreeNode (from @lh-ui/share/core/tree)
ts
export interface LhFlatTreeNode {
  node: LhTreeNode
  key: string
  level: number
  parentKey: string | null
  ancestorKeys: string[]
  isLeaf: boolean
}
LhTreeNode (from @lh-ui/share/core/tree)
ts
export interface LhTreeNode {
  key: string
  label: string
  children?: LhTreeNode[]
  disabled?: boolean
  isLeaf?: boolean
}
LhTreeCheckState (from @lh-ui/share/core/tree)
ts
export type LhTreeCheckState = 'checked' | 'indeterminate' | 'unchecked'

Beauty of tradition, foundation of code