Build Better UIs: A Framework-Agnostic Tree View with Drag & Drop for Vue (and Beyond)
Taming Hierarchy in the UI Jungle
Tree views are everywhere—navigation menus, page builders, file explorers, site maps.
But if you’ve ever tried building one from scratch, you know it’s deceptively complex:
Most solutions are tightly bound to a framework (React or Vue), often fragile under large datasets, or simply not extensible.
I wanted something different.
So I built Node Explorer—a web component that solves all of this while staying framework-agnostic and fully customizable.
Introducing: Node Explorer
Node Explorer is a standalone, modern web component that offers:
- Infinite nesting of tree nodes
- Native drag-and-drop support
- API event system for control (select, expand, drop, etc.)
- Material Design icon support
- Accessible by keyboard
- Fully themeable (dark, light, minimal, high contrast)
- Works with React, Vue, Angular, or plain JavaScript
And it’s open source.
Let me show you how to use it in a real Vue 3 + TypeScript project.
Installation
npm install @jmkcoder/components
Vue 3 + TypeScript Integration
Take your content tree or CMS editor to the next level with a native-feeling file explorer in just a few lines of code.
Here’s what the result looks like when embedded in your app
Lets break it down
📦 Step 1: Import the Component
Lets import the styles and component bundle in your Vue file
import '@jmkcoder/components/dist/components.bundle.css'
import '@jmkcoder/components/dist/index.js'
🧩 Step 2: Wrap in a Local Component
Use the odyssey-node-explorer
as a native Web Component inside a Vue wrapper
<!-- src/components/NodeExplorer.vue -->
<template>
<odyssey-node-explorer
ref="explorer"
:allow-drag-drop="allowDragDrop"
:allow-multi-select="allowMultiSelect"
:theme="theme"
:compact="compact"
/>
</template>
<script lang="ts">
import '@jmkcoder/components/dist/components.bundle.css'
import '@jmkcoder/components/dist/index.js'
import defineComponent, ref, watch, onMounted, onBeforeUnmount from 'vue'
export default defineComponent(
name: 'NodeExplorer',
props:
nodes: Array,
allowDragDrop: type: Boolean, default: true ,
allowMultiSelect: type: Boolean, default: false ,
theme: type: String, default: 'light' ,
compact: type: Boolean, default: false ,
,
emits: ['node-selected'],
setup(props, emit ) null>(null)
const updateNodes = () =>
if (explorer.value)
explorer.value.setAttribute('nodes', JSON.stringify(props.nodes))
const handleNodeSelected = (event: CustomEvent) =>
emit('node-selected', event)
watch(() => props.nodes, updateNodes)
watch(() => props.compact, (newVal) =>
explorer.value?.setAttribute('compact', newVal ? 'true' : 'false')
)
onMounted(() =>
updateNodes()
explorer.value?.addEventListener('node-selected', handleNodeSelected)
)
onBeforeUnmount(() =>
explorer.value?.removeEventListener('node-selected', handleNodeSelected)
)
return explorer
,
)
</script>
🏡 Step 3: Use It in a View
Pass your dynamic data into the explorer with simple bindings.
<!-- src/views/HomeView.vue -->
<template>
<NodeExplorer
:nodes="treeData"
theme="dark"
@node-selected="onNodeSelected"
/>
</template>
<script setup lang="ts">
import NodeExplorer from '../components/NodeExplorer.vue'
import type ExplorerNode from '@jmkcoder/components/dist/components/node-explorer/node-explorer.type'
const treeData: ExplorerNode[] = [
id: 'root',
label: 'My Project',
icon: 'folder',
expanded: true,
children: [
id: 'src',
label: 'src',
icon: 'folder',
children: [
id: 'components',
label: 'components',
icon: 'folder',
children: [
id: 'button.js', label: 'button.js', icon: 'javascript' ,
id: 'input.js', label: 'input.js', icon: 'javascript' ,
],
,
id: 'app.js', label: 'app.js', icon: 'javascript' ,
],
,
],
,
]
const onNodeSelected = (event: CustomEvent) =>
const selectedNode = event.detail.node as ExplorerNode
console.log('Selected:', selectedNode.label)
</script>
🎨 Customizing the Look
Switch between light/dark themes instantly:
<odyssey-node-explorer theme="dark" />
Or go fully custom using CSS variables:
odyssey-node-explorer
--node-label-color: #4caf50;
--node-hover-bg: rgba(76, 175, 80, 0.1);
API Events You Can Hook Into
Event Name | Description |
---|---|
load-children |
Fired when a lazy-loaded node is expanded and needs its children |
node-selected |
Fired when a node is selected |
nodes-selected |
Fired when multiple nodes are selected (when multi-select is enabled) |
nodes-changed |
Fired when nodes structure changes (drag-drop, expand/collapse) |
node-expanded |
Fired when a node is expanded |
node-collapsed |
Fired when a node is collapsed |
drag-start |
Fired when a drag operation starts |
drag-over |
Fired during a drag operation |
drag-leave |
Fired when a drag operation leaves a target |
drop |
Fired when a node is dropped |
These events give you fine-grained control over how you react to user actions, whether you’re syncing state to Vuex, calling an API, or just updating UI.
Why Node Explorer is Different
There are many libraries and plugins that try to solve this.
Node Explorer does it while being:
- Framework-agnostic: works with anything
- Composable: expose only what you need via API
- Extensible: add custom data, event handlers, even logic layers
- Production-ready: optimized rendering and accessibility
Getting Started
Let’s Build Together
This is just the beginning.
Node Explorer will be powering more plugins, editors, and UIs as I work on a full headless CMS platform.
If you’re building something that requires dynamic hierarchy—give this component a try.
If it sparks ideas or could use improvements, I’d love your feedback.
→ Star it. Fork it. Use it.
Let’s explore what’s possible, together.