Skip to content

NbTree and NbTreeNode provide a hierarchical tree view for navigating nested data structures like file systems, project explorers, or organizational charts.

Basic Usage ​

vue
<template>
  <NbTree v-model="selected">
    <NbTreeNode id="1" label="Documents" icon="folder">
      <NbTreeNode id="1.1" label="Resume.pdf" icon="file" />
      <NbTreeNode id="1.2" label="Cover Letter.docx" icon="file" />
    </NbTreeNode>
    <NbTreeNode id="2" label="Images" icon="folder">
      <NbTreeNode id="2.1" label="photo.png" icon="image" />
    </NbTreeNode>
    <NbTreeNode id="3" label="README.md" icon="file" />
  </NbTree>
</template>

<script setup>
import { ref } from 'vue'
const selected = ref(null)
</script>

Branch vs Leaf Nodes ​

Nodes with child NbTreeNode elements are branches -- they show a caret chevron and can be expanded/collapsed. Nodes without children are leaves.

Compact Mode ​

Use the compact prop or size="sm" for denser tree views (24px row height instead of 32px).

Drag and Drop ​

Enable draggable on the tree to allow nodes to be reordered via drag and drop. The tree emits a drop event with the source node, target node, and drop position (before, after, or inside).

vue
<template>
  <NbTree v-model="selected" draggable @drop="onDrop">
    <NbTreeNode id="1" label="Documents" icon="folder">
      <NbTreeNode id="1.1" label="Resume.pdf" icon="file" />
      <NbTreeNode id="1.2" label="Cover Letter.docx" icon="file" />
    </NbTreeNode>
    <NbTreeNode id="2" label="Images" icon="folder">
      <NbTreeNode id="2.1" label="photo.png" icon="image" />
    </NbTreeNode>
    <NbTreeNode id="3" label="README.md" icon="file" />
  </NbTree>
</template>

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

const selected = ref(null)

function onDrop({ sourceId, targetId, position }) {
  console.log(`Move ${sourceId} ${position} ${targetId}`)
  // Reorder your data model here
}
</script>

Visual indicators during drag:

  • Before/After: A colored line appears at the top or bottom edge of the target row.
  • Inside (any node that has not opted out with droppable): The target row highlights with a tinted background and border, indicating the dragged node will become a child.
  • The dragged node fades to 40% opacity while in flight.

Individual nodes can opt out of dragging by setting :draggable="false", or you can enable it per-node instead of globally.

Dropping onto a leaf ​

Every node takes a drop in its middle by default, including a node with no children, which is how a leaf becomes a parent. Set :droppable="false" on a node that should never take children:

vue
<NbTree draggable @drop="onDrop">
  <NbTreeNode
    v-for="page in pages"
    :id="page.id"
    :key="page.id"
    :label="page.title"
  >
    <NbTreeNode
      v-for="child in page.children"
      :id="child.id"
      :key="child.id"
      :label="child.title"
    />
  </NbTreeNode>
  <NbTreeNode id="readme" label="Cannot take children" :droppable="false" />
</NbTree>

A node is a branch when its default slot renders at least one child, checked on every render. A slot that renders nothing (an empty v-for, as for page above before it has children) leaves a plain leaf: no caret, no toggle on click, and no aria-expanded. When the first child arrives the caret appears without the node remounting. For children loaded only on expand, set expandable to show the caret before they exist.

A node can never go inside itself ​

A node dropped into its own subtree is detached from the root, and in a stored tree everything under it disappears. The tree refuses it: while a node is being dragged, itself and every row under it show no drop indicator and fire no drop event, so the pointer keeps the browser's "cannot drop" cursor. Nothing is needed from the product.

Working out the move ​

A drop event says what was dropped where. Turning that into a new tree, and offering the same move from a keyboard "Move to" picker, is the same work in every product, so the library ships it as plain functions. They take any tree of { id, children }, never change the one they are given, and enforce the same subtree rule the drag does.

ts
import { planTreeMove, moveTargets, isInvalidTarget } from '@nubisco/ui'

function onDrop(event) {
  const plan = planTreeMove(pages.value, event.sourceId, {
    kind: event.position, // 'before' | 'after' | 'inside'
    target: event.targetId,
  })
  // null when the move is refused or would change nothing.
  if (!plan) return

  const previous = pages.value
  pages.value = plan.tree // optimistic
  api.move(event.sourceId, plan).catch(() => (pages.value = previous))
}
FunctionAnswers
planTreeMove(tree, id, placement)The tree after the move, plus parentId, index, afterId and beforeId
moveTargets(tree, id)Every node it may move to, in reading order with depth, for a picker
isInvalidTarget(tree, source, id)Whether a target is the node itself or inside it
ancestorsOf(tree, id)The ids above a node, outermost first, to expand its parents
subtreeOf(tree, id)A node and everything under it
nestByDepth(rows)A flat, depth-ordered API response rebuilt into a tree

planTreeMove returns null when the move is refused or would leave the tree as it is, so a product never sends a pointless write. placement is { kind: 'inside' | 'before' | 'after', target } or { kind: 'root' } for the top level. "Inside" appends as the last child.

Actions Slot ​

Use the actions slot on NbTreeNode to render action buttons or badges on the right side. Actions are visible on hover and when the node is selected.

Custom Label Slot ​

Use the label slot to render custom content (e.g., inline rename inputs).

vue
<NbTreeNode id="1" label="Editable">
  <template #label>
    <input v-if="renaming" v-model="name" @blur="save" />
    <span v-else>{{ name }}</span>
  </template>
</NbTreeNode>

Deep Nesting ​

Trees support unlimited nesting depth. Indentation increases by 16px per level.

Programmatic Control ​

Use ref to access expandIds() and collapseAll() methods.

vue
<template>
  <NbButton @click="treeRef.collapseAll()">Collapse All</NbButton>
  <NbTree ref="treeRef">...</NbTree>
</template>

<script setup>
const treeRef = ref()
</script>

Keyboard Navigation ​

KeyAction
Arrow DownMove focus to next visible node
Arrow UpMove focus to previous visible node
Arrow RightExpand a collapsed branch; no effect on leaves
Arrow LeftCollapse an expanded branch
EnterSelect the focused node
HomeMove focus to the first node
EndMove focus to the last visible node
F2Emits dblclick event (for rename workflows)

ARIA Roles ​

  • NbTree renders <ul role="tree">
  • NbTreeNode renders <li role="treeitem">
  • Branch nodes include aria-expanded="true|false"
  • Selected nodes include aria-selected="true"
  • Disabled nodes include aria-disabled="true"
  • Draggable nodes include aria-grabbed="true|false" during drag
  • Child node lists render as <ul role="group">

Focus Management ​

Nodes are focusable via tabindex="-1". Arrow key navigation managed by the tree container. Focus ring uses the primary color via inset box-shadow.

NbTree Props ​

PropTypeDefaultDescription
modelValuestring | nullnullSelected node ID (v-model)
compactbooleanfalseCompact 24px rows
size'sm' | 'md''md'Size variant (sm = compact)
draggablebooleanfalseEnable drag and drop on all nodes

NbTree Events ​

EventPayloadDescription
update:modelValuestringSelected node changed
selectstringNode was selected
togglestring, booleanNode was expanded/collapsed
dropITreeDropEventNode was dropped (drag and drop)

ITreeDropEvent ​

PropertyTypeDescription
sourceIdstringID of the dragged node
targetIdstringID of the drop target node
position'before' | 'after' | 'inside'Where relative to the target

NbTree Methods (via ref) ​

MethodDescription
expandIds(ids: string[])Expand specific nodes
collapseAll()Collapse all nodes

NbTreeNode Props ​

PropTypeDefaultDescription
idstringrequiredUnique node identifier
labelstringrequiredDisplay text
iconstringundefinedIcon name (NbIcon)
disabledbooleanfalseDisable interaction
depthnumber | nullnullNesting depth (auto-computed, 16px per level)
draggableboolean | nullnullOverride tree-level draggable for this node
expandableboolean | nullnullShow the caret. null decides from whether the slot renders children
droppableboolean | nullnullAllow a drop inside, making the dragged node a child. Every node allows one unless set to false

NbTreeNode Slots ​

SlotDescription
defaultChild NbTreeNode elements (makes this a branch)
labelCustom label content (e.g., inline rename input)
actionsRight-side content (visible on hover/selected)

NbTreeNode Events ​

EventPayloadDescription
selectstringNode was clicked
togglestring, booleanBranch expanded/collapsed
contextmenuMouseEvent, stringRight-click on node
dblclickstringDouble-click or F2