Tree view
A standalone tree view component that renders a hierarchical list of nodes with expand/collapse support. Nodes emit click and dblclick events so the parent can react to user interaction. The tree is rendered inline (not in a popup) and includes connecting lines. Every node ends its guide line with a marker of the same size: nodes with children render it as the expand toggle holding the chevron, leaf nodes as a plain circle. Markers can be colored per node or per depth level.
Required icons
Props
expanded-depth?: number
The number of levels that are visible without interaction. `1` shows the roots only, `2` the roots and their children, `Infinity` expands the whole tree. Nodes expanded or collapsed by the user keep their state until the options change.
Default: 1
level-colors?: (FluxColor | string)[]
An array of colors per depth level, used as a fallback for nodes that have no `color` of their own. Index 0 = root level, index 1 = first child level, etc. Each entry can be a `FluxColor` name (e.g. `primary`, `danger`) or any CSS color string (e.g. `#6366f1`). Levels without an entry keep the neutral marker.
options: FluxTreeViewOption[]
The tree structure of nodes to display.
Emits
click: [FluxTreeViewOption]
Triggered when a node is clicked.
dblclick: [FluxTreeViewOption]
Triggered when a node is double-clicked. Also toggles expand/collapse if the node has children.
Slots
trailing
Content rendered after the label of every node, e.g. a badge or action. Receives the flattened node via the `node` slot prop.
Option object
Each entry in the options array (and nested children arrays) is a FluxTreeViewOption:
idstring | numberlabelstringiconFluxIconNamecolorFluxColor | stringFluxColor name or any CSS color string. Takes precedence over level-colors.disabledbooleanclick and dblclick. Its marker stays clickable, so a disabled branch can still be expanded.childrenFluxTreeViewOption[]Keyboard navigation
click for the highlighted node.Accessibility
The tree is a single composite widget: the role="tree" container is the only tab stop and exposes the highlighted node through aria-activedescendant, while each node carries role="treeitem" with aria-level, aria-selected and (when it has children) aria-expanded. Parent → child grouping is conveyed with aria-owns rather than nested role="group" wrappers, so the flat DOM stays valid for assistive tech. Set a descriptive aria-label to name the tree.
The highlight is tracked by node identity: when the visible set changes (for example a node above the highlight collapses), the highlight stays on the same node instead of jumping to whatever now occupies that position.
Examples
Basic
A tree view with per-level colors.
<template>
<FluxPane style="max-width: 390px">
<FluxPaneBody>
<FluxTreeView
:level-colors="['primary', 'info', 'success']"
:options="options"/>
</FluxPaneBody>
</FluxPane>
</template>
<script
lang="ts"
setup>
import { FluxPane, FluxPaneBody, FluxTreeView } from '@flux-ui/components';
import type { FluxTreeViewOption } from '@flux-ui/types';
const options: FluxTreeViewOption[] = [
{
id: 1,
label: 'Electronics',
children: [
{
id: 2,
label: 'Computers',
children: [
{ id: 3, label: 'Laptops' },
{ id: 4, label: 'Desktops' },
{ id: 5, label: 'Tablets' }
]
},
{
id: 6,
label: 'Phones',
children: [
{ id: 7, label: 'Smartphones' },
{ id: 8, label: 'Feature phones' }
]
}
]
},
{
id: 9,
label: 'Clothing',
children: [
{ id: 10, label: 'Men' },
{ id: 11, label: 'Women' },
{ id: 12, label: 'Kids' }
]
},
{
id: 13,
label: 'Home & Garden',
children: [
{ id: 14, label: 'Furniture' },
{ id: 15, label: 'Garden tools' }
]
}
];
</script>Expanded depth
With expanded-depth, the tree opens with more than just its roots visible. 2 shows the roots and their children.
<template>
<FluxPane style="max-width: 390px">
<FluxPaneBody>
<FluxTreeView
:expanded-depth="2"
:level-colors="['primary', 'info', 'success']"
:options="options"/>
</FluxPaneBody>
</FluxPane>
</template>
<script
lang="ts"
setup>
import { FluxPane, FluxPaneBody, FluxTreeView } from '@flux-ui/components';
import type { FluxTreeViewOption } from '@flux-ui/types';
const options: FluxTreeViewOption[] = [
{
id: 1,
label: 'Electronics',
children: [
{
id: 2,
label: 'Computers',
children: [
{ id: 3, label: 'Laptops' },
{ id: 4, label: 'Desktops' }
]
},
{
id: 5,
label: 'Phones',
children: [
{ id: 6, label: 'Smartphones' },
{ id: 7, label: 'Feature phones' }
]
}
]
},
{
id: 8,
label: 'Clothing',
children: [
{ id: 9, label: 'Men' },
{ id: 10, label: 'Women' }
]
}
];
</script>Events
Responding to click and double-click events.
<template>
<FluxPane style="max-width: 390px">
<FluxPaneBody>
<FluxTreeView
:level-colors="['primary', 'info', 'success']"
:options="options"
@click="onItemClick"
@dblclick="onItemDblClick"/>
</FluxPaneBody>
<FluxPaneBody v-if="lastEvent">
<p style="font-size: 14px; margin: 0;">
<strong>{{ lastEvent.type }}</strong>: {{ lastEvent.label }}
<span style="color: var(--foreground-secondary);">(id: {{ lastEvent.id }})</span>
</p>
</FluxPaneBody>
</FluxPane>
</template>
<script
lang="ts"
setup>
import { FluxPane, FluxPaneBody, FluxTreeView } from '@flux-ui/components';
import type { FluxTreeViewOption } from '@flux-ui/types';
import { ref } from 'vue';
const lastEvent = ref<{ type: string; id: string | number; label: string } | null>(null);
const options: FluxTreeViewOption[] = [
{
id: 1,
label: 'Electronics',
children: [
{ id: 2, label: 'Laptops' },
{ id: 3, label: 'Desktops' },
{ id: 4, label: 'Smartphones' }
]
},
{
id: 5,
label: 'Clothing',
children: [
{ id: 6, label: 'Men' },
{ id: 7, label: 'Women' }
]
}
];
function onItemClick(option: FluxTreeViewOption): void {
lastEvent.value = { type: 'click', id: option.id, label: option.label };
}
function onItemDblClick(option: FluxTreeViewOption): void {
lastEvent.value = { type: 'dblclick', id: option.id, label: option.label };
}
</script>Trailing
Rendering a badge after each node label with the trailing slot.
<template>
<FluxPane style="max-width: 390px">
<FluxPaneBody>
<FluxTreeView
aria-label="Product categories"
:level-colors="['primary', 'info', 'success']"
:options="options">
<template #trailing="{ node }">
<FluxBadge
v-if="counts[node.id]"
:label="`${counts[node.id]}`"/>
</template>
</FluxTreeView>
</FluxPaneBody>
</FluxPane>
</template>
<script
lang="ts"
setup>
import { FluxBadge, FluxPane, FluxPaneBody, FluxTreeView } from '@flux-ui/components';
import type { FluxTreeViewOption } from '@flux-ui/types';
const counts: Record<string | number, number> = {
3: 12,
4: 8,
5: 4,
7: 21,
8: 3
};
const options: FluxTreeViewOption[] = [
{
id: 1,
label: 'Electronics',
children: [
{
id: 2,
label: 'Computers',
children: [
{ id: 3, label: 'Laptops' },
{ id: 4, label: 'Desktops' },
{ id: 5, label: 'Tablets' }
]
},
{
id: 6,
label: 'Phones',
children: [
{ id: 7, label: 'Smartphones' },
{ id: 8, label: 'Feature phones' }
]
}
]
}
];
</script>